This R script is used to validate the points in the ReSurvey database
using RS indicators (NDVI, NDMI, canopy height).
Some data managenemt
Several EUNIS level 1 assigned
Number of rows where there is more than one EUNIS 1 assigned, and
they are different among them. See what to do with these later! So far I
take EUNISa_1.
nrow(db_resurv_RS %>%
# Rows with more than one EUNIS 1 assigned
filter(!is.na(EUNISb_1)) %>%
filter(EUNISa_1!=EUNISb_1 | EUNISb_1 != EUNISc_1 | EUNISa_1 != EUNISc_1))
[1] 111
See “confusions”:
db_resurv_RS %>%
# Rows with more than one EUNIS 1 assigned
filter(!is.na(EUNISb_1)) %>%
filter(EUNISa_1!=EUNISb_1 | EUNISb_1 != EUNISc_1 | EUNISa_1 != EUNISc_1) %>%
distinct(EUNISa_1, EUNISb_1, EUNISc_1, EUNISd_1)
Define “confusion” columns:
db_resurv_RS <- db_resurv_RS %>%
mutate(EUNIS1_conf_type = case_when(
EUNISa_1 == "R" & EUNISb_1 == "S" ~ "R/S",
EUNISa_1 == "S" & EUNISb_1 == "T" ~ "S/T",
EUNISa_1 == "R" & EUNISb_1 == "R" & EUNISc_1 == "S" ~ "R/S",
EUNISa_1 == "R" & EUNISb_1 == "R" & EUNISc_1 == "S" & EUNISd_1 == "S" ~ "R/S",
EUNISa_1 == "P" & EUNISb_1 == "Q" ~ "P/Q",
TRUE ~ NA_character_),
EUNIS1_conf = !is.na(EUNIS1_conf_type))
Tibble with selected columns
db_resurv_RS_short <- db_resurv_RS %>%
select(PlotObservationID, Country, RS_CODE, `ReSurvey site`, `ReSurvey plot`,
`Manipulate (y/n)`, `Type of manipulation`, Lon_updated, Lat_updated,
`Location method`, `Location uncertainty (m)`, EUNISa_1,
EUNISa_1_descr, EUNISa_2, EUNISa_2_descr, EUNISa_3, EUNISa_3_descr,
EUNISa_4, EUNISa_4_descr, EUNIS1_conf, EUNIS1_conf_type,
date, year, biogeo, unit, year_RS, Lon_RS, Lat_RS,
starts_with("NDVI"), starts_with("NDMI"), starts_with("NDWI"),
starts_with("EVI"), starts_with("SAVI"), canopy_height,
# SOS_DOY, SOS_date, NDVI_at_SOS, Peak_DOY, Peak_date, NDVI_at_Peak,
# EOS_DOY, EOS_date, NDVI_at_EOS, Season_Length,
S2_data, RS_data,
# S2_phen_data,
CH_data)
TO-DO: Missing data checks
Do when all RS data is ready!
Flag when year is different between RS data and ReSurvey db
db_resurv_RS_short <- db_resurv_RS_short %>%
mutate(year_diff = year != year_RS)
db_resurv_RS_short %>% count(year_diff)
None with different year so far.
Flag when coordinates are different between RS data and ReSurvey
db
db_resurv_RS_short <- db_resurv_RS_short %>%
mutate(Lon_diff = case_when(Lon_updated == Lon_RS ~ "NO",
# Sometimes they are only slighly different
abs(Lon_updated - Lon_RS) < 0.01 ~ "SMALL",
is.na(Lon_updated) | is.na(Lon_RS) ~ NA,
TRUE ~ "LARGE"),
Lat_diff = case_when(Lat_updated == Lat_RS ~ "NO",
# Sometimes they are only slighly different
abs(Lat_updated - Lat_RS) < 0.01 ~ "SMALL",
is.na(Lat_updated) | is.na(Lat_RS) ~ NA,
TRUE ~ "LARGE"))
db_resurv_RS_short %>% count(Lon_diff)
db_resurv_RS_short %>% count(Lat_diff)
None with differences.
Handle plots that have more than one obs per year
Add column PLOT to data to identify unique plots:
db_resurv_RS_short_PLOT <- db_resurv_RS_short %>%
# Original names give problems, create new vars
mutate(RS_site = `ReSurvey site`, RS_plot = `ReSurvey plot`) %>%
# Convert to data.table for faster processing
lazy_dt() %>%
# Group by the 3 vars that uniquely identify each plot
group_by(RS_CODE, RS_site, RS_plot) %>%
# Create a new variable PLOT for each group
mutate(PLOT = .GRP) %>%
# Convert back to tibble
as_tibble() %>%
# Remove unneeded vars
select(-RS_site, -RS_plot)
There should be only one observation of each plot per year.
Plots where there is at least a year with more than one observation,
and where those observations have a different EUNIS assigned:
plots_to_remove <- db_resurv_RS_short_PLOT %>%
group_by(PLOT, year) %>%
summarize(EUNISa_1_n = n_distinct(EUNISa_1, na.rm = TRUE)) %>%
ungroup() %>%
filter(EUNISa_1_n > 1) %>%
distinct(PLOT)
`summarise()` has grouped output by 'PLOT'. You can override using the `.groups` argument.
Remove plots_to_remove from the database:
db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
anti_join(plots_to_remove, by = "PLOT")
Plots and years where there is more than one observation:
plots_to_merge <- db_resurv_RS_short_PLOT %>%
group_by(PLOT, year) %>%
# Plots that have more than one observation per year
filter(n() > 1) %>%
ungroup() %>%
distinct(PLOT)
Summarize plots_to_merge:
plots_to_merge_summ <- db_resurv_RS_short_PLOT %>%
group_by(PLOT, year) %>%
# Plots that have more than one observation per year
filter(n() > 1) %>%
mutate(obs_num = row_number()) %>%
pivot_wider(
names_from = obs_num,
values_from = c(date, PlotObservationID),
names_prefix = "obs_"
) %>%
arrange(PLOT) %>%
summarize(
across(c(Country, RS_CODE, `ReSurvey site`, `ReSurvey plot`,
`Manipulate (y/n)`, `Type of manipulation`, Lon_updated,
Lat_updated, `Location method`, `Location uncertainty (m)`,
EUNISa_1, EUNISa_1_descr, EUNISa_2, EUNISa_2_descr, EUNISa_3,
EUNISa_3_descr, EUNISa_4, EUNISa_4_descr, EUNIS1_conf,
EUNIS1_conf_type, biogeo, unit, year_RS, Lon_RS, Lat_RS, NDVI_max,
NDVI_median, NDVI_min, NDVI_mode, NDVI_p10, NDVI_p90, NDMI_max,
NDMI_median, NDMI_min, NDMI_mode, NDMI_p10, NDMI_p90, NDWI_max,
NDWI_median, NDWI_min, NDWI_mode, NDWI_p10, NDWI_p90, EVI_max,
EVI_median, EVI_min, EVI_mode, EVI_p10, EVI_p90, SAVI_max,
SAVI_median, SAVI_min, SAVI_mode, SAVI_p10, SAVI_p90,
canopy_height,
# SOS_DOY, SOS_date, Peak_DOY, Peak_date, EOS_DOY, EOS_date,
S2_data, RS_data, CH_data,
# S2_phen_data,
year_diff,
Lon_diff, Lat_diff), first),
across(starts_with("date_obs_"), min),
across(starts_with("PlotObservationID_obs_"), min)
) %>%
ungroup()
`summarise()` has grouped output by 'PLOT'. You can override using the `.groups` argument.
Remove plots_to_merge from the database:
db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
anti_join(plots_to_merge, by = "PLOT")
And add plots_to_merge_summ, where each plot and year only has one
row:
db_resurv_RS_short_PLOT <- bind_rows(db_resurv_RS_short_PLOT,
plots_to_merge_summ)
Check that there is only one row per plot and per year:
db_resurv_RS_short_PLOT %>%
group_by(PLOT, year) %>%
# Plots that have more than one observation per year
filter(n() > 1)
So, to sum up what I have done:
- Plots where there is at least a year with more than one observation,
and where those observations have a different EUNIS assigned: Plots
REMOVED from the data
- Plots where there is more than one observation, but observations
have the same EUNIS assigned: kept in the data. Merged so that there is
only one row per year. Info about the different dates (when different)
is kept in columns date_obs_1 - date_obs_40, and info about the
different PlotObservationID is kept in the columns
PlotObservationID_obs_1 - PlotObservationID_obs_40.
Save to clean data
Save clean file for analyses (to be updated continuously due to
updates in ReSurvey database and updates on RS data).
write_tsv(db_resurv_RS_short_PLOT,
here("data", "clean","db_resurv_RS_short_PLOT_20250610.csv"))
Distributions all bioregions
# Define a function to create histograms
plot_histogram <- function(data, x_var, x_label) {
ggplot(data %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
aes(x = !!sym(x_var))) +
geom_histogram(color = "black", fill = "white") +
labs(x = x_label, y = "Frequency") +
theme_bw()
}
# Define a function to create plots with violin + boxplot + points
distr_plot <- function(data, y_vars, y_labels) {
for (i in seq_along(y_vars)) {
y_var <- y_vars[[i]]
y_label <- y_labels[[i]]
p <- ggplot(data = data %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
label = length(x)),
geom = "text", aes(label = ..label..), vjust = 0.5) +
labs(y = y_label, x = "EUNIS level 1") +
scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
guides(fill = FALSE, color = FALSE) +
theme_bw() + coord_flip()
print(p)
}
}
NDVI, NDMI, NDWI, SAVI and EVI
Ranges of min and max:
range(db_resurv_RS_short_PLOT$NDVI_max, na.rm = T)
[1] -0.1241867 1.0000000
range(db_resurv_RS_short_PLOT$NDMI_max, na.rm = T)
[1] -0.3917787 0.9998277
range(db_resurv_RS_short_PLOT$NDWI_max, na.rm = T)
[1] -0.5834020 0.9982833
range(db_resurv_RS_short_PLOT$SAVI_max, na.rm = T) # SAVI_max > 1!
[1] -0.1548054 1.1419991
range(db_resurv_RS_short_PLOT$EVI_max, na.rm = T) # EVI_max > 1!
[1] -1.170776e-01 2.251800e+14
range(db_resurv_RS_short_PLOT$NDVI_min, na.rm = T)
[1] -0.9997430 0.5470833
range(db_resurv_RS_short_PLOT$NDMI_min, na.rm = T)
[1] -0.9997549 0.6268497
range(db_resurv_RS_short_PLOT$NDWI_min, na.rm = T)
[1] -1.0000000 0.4160919
range(db_resurv_RS_short_PLOT$SAVI_min, na.rm = T)
[1] -0.9129567 0.7278759
range(db_resurv_RS_short_PLOT$EVI_min, na.rm = T) # EVI_min > 1!
[1] -3.479031e+14 2.495464e+00
nrow(db_resurv_RS_short_PLOT %>% filter(if_any(starts_with("NDVI"), ~ .x > 1)))
[1] 0
nrow(db_resurv_RS_short_PLOT %>% filter(if_any(starts_with("NDMI"), ~ .x > 1)))
[1] 0
nrow(db_resurv_RS_short_PLOT %>% filter(if_any(starts_with("NDWI"), ~ .x > 1)))
[1] 0
nrow(db_resurv_RS_short_PLOT %>% filter(if_any(starts_with("SAVI"), ~ .x > 1)))
[1] 7
nrow(db_resurv_RS_short_PLOT %>% filter(if_any(starts_with("EVI"), ~ .x > 1)))
[1] 49292
Histograms to check that max and min values are ok:
plot_histogram(db_resurv_RS_short_PLOT, "NDVI_max", "NDVI max")

plot_histogram(db_resurv_RS_short_PLOT, "NDMI_max", "NDMI max")

plot_histogram(db_resurv_RS_short_PLOT, "NDWI_max", "NDWI max")

plot_histogram(db_resurv_RS_short_PLOT, "SAVI_max", "SAVI max")

plot_histogram(db_resurv_RS_short_PLOT %>%
# Some values wrong!
filter(EVI_max <= 1), "EVI_max", "EVI max")

plot_histogram(db_resurv_RS_short_PLOT, "NDVI_min", "NDVI min")

plot_histogram(db_resurv_RS_short_PLOT, "NDMI_min", "NDMI min")

plot_histogram(db_resurv_RS_short_PLOT, "NDWI_min", "NDWI min")

plot_histogram(db_resurv_RS_short_PLOT, "SAVI_min", "SAVI min")

plot_histogram(db_resurv_RS_short_PLOT %>%
# Some values wrong!
filter(EVI_min >= -1 & EVI_min <= 1), "EVI_min", "EVI min")

nrow(db_resurv_RS_short_PLOT %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
filter(EVI_max > 1))
[1] 49113
db_resurv_RS_short_PLOT %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q"))%>%
filter(EVI_max > 1) %>%
count(biogeo, unit)
So far, do not use EVI values because they seem to be wrong.
Distribution plots:
distr_plot(db_resurv_RS_short_PLOT,
c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"),
c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))




distr_plot(db_resurv_RS_short_PLOT,
c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"),
c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))




distr_plot(db_resurv_RS_short_PLOT,
c("NDWI_max", "NDWI_p90", "NDWI_min", "NDWI_p10"),
c("NDWI max", "NDWI p90", "NDWI min", "NDWI p10"))




distr_plot(db_resurv_RS_short_PLOT,
c("SAVI_max", "SAVI_p90", "SAVI_min", "SAVI_p10"),
c("SAVI max", "SAVI p90", "SAVI min", "SAVI p10"))




# Define a function to create plots with violin + boxplot + points
# Facetted by S2_data
distr_plot_sensor <- function(data, y_vars, y_labels) {
for (i in seq_along(y_vars)) {
y_var <- y_vars[[i]]
y_label <- y_labels[[i]]
p <- ggplot(data = data %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
label = length(x)),
geom = "text", aes(label = ..label..), vjust = 0.5) +
labs(y = y_label, x = "EUNIS level 1") +
scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
guides(fill = FALSE, color = FALSE) +
theme_bw() + coord_flip() + facet_wrap(~ S2_data)
print(p)
}
}
Distribution plots by sensor:
distr_plot_sensor(db_resurv_RS_short_PLOT,
c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"),
c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))




distr_plot_sensor(db_resurv_RS_short_PLOT,
c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"),
c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))




distr_plot_sensor(db_resurv_RS_short_PLOT,
c("NDWI_max", "NDWI_p90", "NDWI_min", "NDWI_p10"),
c("NDWI max", "NDWI p90", "NDWI min", "NDWI p10"))




distr_plot_sensor(db_resurv_RS_short_PLOT,
c("SAVI_max", "SAVI_p90", "SAVI_min", "SAVI_p10"),
c("SAVI max", "SAVI p90", "SAVI min", "SAVI p10"))




CH
distr_plot(db_resurv_RS_short_PLOT, "canopy_height", "Canopy height (m)")
Show habitats with CH categories
ggplot(db_resurv_RS_short_PLOT %>%
# Keep only forests, grasslands, shrublands and wetlands
filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
mutate(CH_cat =
factor(
case_when(canopy_height == 0 ~ "0 m",
canopy_height > 0 & canopy_height <= 1 ~ "0-1 m",
canopy_height > 1 & canopy_height <=2 ~ "1-2 m",
canopy_height > 2 & canopy_height <=5 ~ "2-5 m",
canopy_height > 5 & canopy_height <=8 ~ "5-8 m",
canopy_height > 8 ~ "> 8 m",
is.na(canopy_height) ~ NA_character_),
levels = c(
"0 m", "0-1 m", "1-2 m", "2-5 m", "5-8 m", "> 8 m"))),
aes(x = EUNISa_1_descr, fill = CH_cat)) +
geom_bar() + theme_bw() + coord_flip() +
scale_y_continuous(labels = label_number()) +
scale_fill_viridis_d(direction = -1) +
labs(x = "EUNIS level 1", fill = "Canopy height") +
scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
theme(legend.position = c(0.8, 0.75),
legend.direction = "vertical")
Stats per habitat type
db_resurv_RS_short_PLOT %>%
# Keep only forests, grasslands, shrublands and wetlands
filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
group_by(EUNISa_1_descr) %>%
summarise(across(canopy_height, list(
mean = mean,
median = median,
sd = sd,
min = min,
max = max
), na.rm = TRUE))
Phenology
Calculate metrics
db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
mutate(
# Difference NDVI between Peak and SOS
diff_Peak_SOS = NDVI_at_Peak - NDVI_at_SOS,
# Difference NDVI between Peak and EOS
diff_Peak_EOS = NDVI_at_Peak - NDVI_at_EOS)
Histograms phenology measures
ggplot(data = db_resurv_RS_short_PLOT %>%
# Keep only forests, grasslands, shrublands and wetlands
filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
pivot_longer(cols = c(SOS_DOY, Peak_DOY, EOS_DOY), names_to = "name",
values_to = "value"),
aes(x = value)) +
geom_histogram(fill = "white", color = "black") +
facet_grid(biogeo ~ name, scales = "free_y") +
theme_bw()
ggplot(data = db_resurv_RS_short_PLOT %>%
# Keep only forests, grasslands, shrublands and wetlands
filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
pivot_longer(cols = c(NDVI_at_SOS, NDVI_at_Peak, NDVI_at_EOS),
names_to = "name", values_to = "value"),
aes(x = value)) +
geom_histogram(fill = "white", color = "black") +
facet_grid(biogeo ~ name, scales = "free_y") +
theme_bw()
ggplot(data = db_resurv_RS_short_PLOT %>%
# Keep only forests, grasslands, shrublands and wetlands
filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
pivot_longer(cols = c(diff_Peak_SOS, diff_Peak_EOS),
names_to = "name", values_to = "value"),
aes(x = value)) +
geom_histogram(fill = "white", color = "black") +
facet_grid(biogeo ~ name, scales = "free_y") +
theme_bw()
ggplot(data = db_resurv_RS_short_PLOT %>%
# Keep only forests, grasslands, shrublands and wetlands
filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
pivot_longer(cols = c(Season_Length),
names_to = "name", values_to = "value"),
aes(x = value)) +
geom_histogram(fill = "white", color = "black") +
facet_grid(biogeo ~ name, scales = "free_y") +
theme_bw()
Distributions
distr_plot(db_resurv_RS_short_PLOT,
c("SOS_DOY","Peak_DOY", "EOS_DOY",
"NDVI_at_SOS", "NDVI_at_Peak", "NDVI_at_EOS",
"diff_Peak_SOS","diff_Peak_EOS", "Season_Length"),
c("SOS DOY", "Peak DOY", "EOS DOY",
"NDVI at SOS", "NDVI at Peak", "NDVI at EOS",
"Difference Peak-SOS", "Difference Peak-EOS", "Season Length"))
Distributions per bioregion
# Define a function to create plots with violin + boxplot + points
distr_plot_biogeo <- function(data, y_vars, y_labels) {
plots <- list()
for (i in seq_along(y_vars)) {
y_var <- y_vars[[i]]
y_label <- y_labels[[i]]
p <- ggplot(data = data %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
label = length(x)),
geom = "text", aes(label = ..label..), vjust = 0.5) +
labs(y = y_label, x = "EUNISa_1_descr") +
scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
guides(fill = FALSE, color = FALSE) +
theme_bw() + coord_flip() + facet_wrap(~ biogeo)
plots[[y_var]] <- p
}
return(plots)
}
NDVI, NDMI, NDWI, SAVI and EVI
Distribution plots:
distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"),
c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))
distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"),
c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))
distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
c("NDWI_max", "NDWI_p90", "NDWI_min", "NDWI_p10"),
c("NDWI max", "NDWI p90", "NDWI min", "NDWI p10"))
distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
c("SAVI_max", "SAVI_p90", "SAVI_min", "SAVI_p10"),
c("SAVI max", "SAVI p90", "SAVI min", "SAVI p10"))
distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)) %>%
filter(EVI_max <= 1) %>%
filter(EVI_min >= -1 & EVI_min <= 1),
c("EVI_max", "EVI_p90", "EVI_min", "EVI_p10"),
c("EVI max", "EVI p90", "EVI min", "EVI p10"))
CH
distr_plot_biogeo(db_resurv_RS_short_PLOT, "canopy_height", "Canopy height (m)")
In this plot, those with biogeo = NA are those that do not have S2 or
Landsat data (and thus biogeo has not been assigned), but have CH data.
We should later assign a biogeo based on location.
Phenology
distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
c("SOS_DOY", "Peak_DOY", "EOS_DOY",
"NDVI_at_SOS", "NDVI_at_Peak", "NDVI_at_EOS",
"diff_Peak_SOS", "diff_Peak_EOS", "Season_Length"),
c("SOS DOY", "Peak DOY", "EOS DOY",
"NDVI at SOS", "NDVI at Peak", "NDVI at EOS",
"Difference Peak-SOS", "Difference Peak-EOS",
"Season Length"))
BOR missing because there is no phenology info for EUNISa_1 %in%
c(“T”, “R”, “S”, “Q”).
First validation
For T, R, S, Q habitats.
Define a set of rules for a first validation of ALL ReSurvey data. We
can call these “Expert-based” rules.
Number of observations in ReSurvey from the habitats of interest:
nrow(db_resurv_RS_short_PLOT %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q")))
Number of observations in ReSurvey from the habitats of interest and
with all RS data:
nrow(db_resurv_RS_short_PLOT %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
filter(CH_data == T) %>%
filter(S2_data == T | Landsat_data ==T) %>%
filter(S2_phen_data == T))
db_resurv_RS_short_PLOT_terrestrial <- db_resurv_RS_short_PLOT %>%
filter(EUNISa_1 %in% c("T", "R", "S", "Q"))
Define rules
Create column for first validation based on different indicators,
where “wrong” is noted when the validation rule is not met. Include
EUNIS1 confusions.
db_resurv_RS_short_PLOT_terrestrial %>% count(EUNISa_1, EUNIS1_conf_type)
Define rules:
db_resurv_RS_short_PLOT_terrestrial <-
db_resurv_RS_short_PLOT_terrestrial %>%
mutate(
valid_1_NDWI = case_when(
# Points that are basically water
NDWI_max > 0.3 ~ "wrong",
TRUE ~ NA_character_),
valid_1_CH = case_when(
# T points with low CH
EUNISa_1 == "T" & canopy_height < 8 ~ "wrong",
# S points with low CH
EUNISa_1 =="S" & canopy_height < 5 ~ "wrong",
# R & Q points with high CH
EUNISa_1 %in% c("R", "Q") & canopy_height > 2 ~ "wrong",
TRUE ~ NA_character_),
valid_1_NDVI = case_when(
# T points with low NDVI_max
EUNISa_1 == "T" & NDVI_max < 0.6 ~ "wrong",
# S-R-Q points with low NDVI_max
EUNISa_1 %in% c("R", "S", "Q") & NDVI_max < 0.2 ~ "wrong",
TRUE ~ NA_character_),
# Count how many validation rules are not met
valid_1_count = rowSums(across(c(valid_1_NDWI, valid_1_CH, valid_1_NDVI),
~ . == "wrong"), na.rm = TRUE),
# Points where at least 1 rule not met
valid_1 = if_else(valid_1_count > 0, "At least 1 rule broken",
"No rules broken so far")
)
Plots first validation
ggplot(db_resurv_RS_short_PLOT_terrestrial%>%
mutate(rules_broken = case_when(
valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
valid_1_count == 2 &
valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
valid_1_count == 2 &
valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
valid_1_count == 2 &
valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
valid_1_count == 3 ~ "NDWI + NDVI + CH",
TRUE ~ NA_character_
)),
aes(x = valid_1_count, fill = rules_broken)) +
geom_bar() + labs(x = "Number of broken rules")
db_resurv_RS_short_PLOT_terrestrial %>%
mutate(rules_broken = case_when(
valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
valid_1_count == 2 &
valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
valid_1_count == 2 &
valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
valid_1_count == 2 &
valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
valid_1_count == 3 ~ "NDWI + NDVI + CH",
TRUE ~ NA_character_
)) %>%
count(rules_broken, EUNIS1_conf_type)
Proportion of observations not validated (so far):
nrow(db_resurv_RS_short_PLOT_terrestrial %>% filter(valid_1_count > 0))/
nrow(db_resurv_RS_short_PLOT_terrestrial)
But be aware that there are still MANY missing RS data.
ggplot(db_resurv_RS_short_PLOT_terrestrial %>%
mutate(diff_GPS = if_else(
`Location method` != "Location with differential GPS" |
is.na(`Location method`), "no", "yes")),
aes(x = diff_GPS, fill = valid_1)) +
geom_bar() + labs(x = "Differential GPS")
ggplot(db_resurv_RS_short_PLOT_terrestrial %>%
mutate(GPS = case_when(
`Location method` == "Location with differential GPS" ~ "yes",
`Location method` == "Location with GPS" ~ "yes",
is.na(`Location method`) ~ "no",
TRUE ~ "no"
)),
aes(x = GPS, fill = valid_1)) +
geom_bar() + labs(x = "GPS")
Points with any rule broken and confusion between EUNIS:
nrow(db_resurv_RS_short_PLOT_terrestrial %>%
filter(EUNIS1_conf == T & valid_1_count > 0))
Convert to shp to look at these in GIS:
# st_write(db_resurv_RS_short_PLOT_terrestrial %>%
# filter(EUNIS1_conf == T & valid_1_count > 0) %>%
# st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
# "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_EUNIS_conf.shp")
Checked and yes
How many points with differential GPS that have at least 1 rule
broken?
nrow(db_resurv_RS_short_PLOT_terrestrial %>%
filter(`Location method` == "Location with differential GPS" &
valid_1 == "At least 1 rule broken"))
Convert to shp to look at these in GIS:
# st_write(db_resurv_RS_short_PLOT_terrestrial %>%
# filter(`Location method` == "Location with differential GPS" &
# valid_1 == "At least 1 rule broken") %>%
# st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
# "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_diff_GPS.shp")
RF models
Using the conditional inference version of random forest (cforest in
package party). Suggested if the data are highly correlated. Cforest is
more stable in deriving variable importance values in the presence of
highly correlated variables, thus providing better accuracy in
calculating variable importance (ref below).
Hothorn, T., Hornik, K. and Zeileis, A. (2006) Unbiased Recursive
Portioning: A Conditional Inference Framework. Journal of Computational
and Graphical Statistics, 15, 651- 674. http://dx.doi.org/10.1198/106186006X133933
All GPS points
filtered_data0 <- all_GPS_valid %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices0 <- sample(1:nrow(filtered_data0), 0.7 * nrow(filtered_data0))
train_data0 <- filtered_data0[train_indices0, ]
test_data0 <- filtered_data0[-train_indices0, ]
Number of points per category for filtered data:
filtered_data0 %>% count(EUNISa_1)
rf_cforest0 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data0,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest0 <- predict(rf_cforest0, newdata = test_data0,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest0, test_data0$EUNISa_1)
varimp_rf_cforest0 <- party::varimp(rf_cforest0, conditional = F)
Variable Importance Plot
varimp_rf_cforest0_df <- data.frame(Variable = names(varimp_rf_cforest0),
Importance = varimp_rf_cforest0)
ggplot(varimp_rf_cforest0_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest0, newdata = test_data0, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data0$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc0 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc0
REVISE FROM HERE: All GPS points above p20
Filter the data to get only GPS-points above p20 of NDVI_max and
NDMI_min.
all_GPS_valid <- all_GPS_valid %>%
select(-percentile_20_NDVI_max, -percentile_20_NDMI_min)
percentiles <- all_GPS_valid %>%
group_by(EUNISa_1) %>%
summarize(
percentile_20_NDVI_max = quantile(NDVI_max, 0.20, na.rm = T),
percentile_20_NDMI_min = quantile(NDMI_min, 0.20, na.rm = T),
percentile_80_NDVI_max = quantile(NDVI_max, 0.80, na.rm = T),
percentile_80_NDMI_min = quantile(NDMI_min, 0.80, na.rm = T)
)
# Join the percentiles back to the original data
all_GPS_valid <- all_GPS_valid %>%
left_join(percentiles, by = "EUNISa_1")
# Filter rows above the 20th percentile for both variables for each category of EUNISa_1
filtered_data1 <- all_GPS_valid %>%
filter(
NDVI_max >= percentile_20_NDVI_max & NDMI_min >= percentile_20_NDMI_min
) %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices1 <- sample(1:nrow(filtered_data1), 0.7 * nrow(filtered_data1))
train_data1 <- filtered_data1[train_indices1, ]
test_data1 <- filtered_data1[-train_indices1, ]
Number of points per category for filtered data:
filtered_data1 %>% count(EUNISa_1)
Investigate package ggparty (e.g. autoplot function, and more).
TO-DO: Choose the hyperparameter mtry based on the square root of the
number of predictor variables (Hastie et al., 2009)-
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements
of statistical learning: Data mining, inference, and prediction.
Springer Science & Business Media.
Maybe TO_DO: We variated ntree from 50 to 800 in steps of 50, leaving
mtry constant at 2. Tis parameter variation showed that ntree=500 was
optimal, while higher ntree led to no further model improvement
(Supplementary Fig. S10). Subsequently, the hyperparameter mtry was
varied from 2 to 8 with constant ntree=500. Here, mtry=3 led to the best
results in almost all cases (Supplementary Fig. S11). Consequently, we
chose ntree=500 and mtry=3 for our main analysis across all study
sites.
rf_cforest1 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data1,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest1 <- predict(rf_cforest1, newdata = test_data1,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest1, test_data1$EUNISa_1)
SurrogateTree –> does not work
varimp_rf_cforest1 <- party::varimp(rf_cforest1, conditional = F)
Variable Importance Plot
varimp_rf_cforest1_df <- data.frame(Variable = names(varimp_rf_cforest1),
Importance = varimp_rf_cforest1)
ggplot(varimp_rf_cforest1_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
Tree Visualization
# Create a single conditional inference tree using ctree
single_tree1 <- ctree(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max + NDMI_min +
NDWI_max + NDWI_min + EVI_max + EVI_min + SAVI_max +
SAVI_min + canopy_height,
data = train_data1)
# Plot the single tree using
autoplot(single_tree1)
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc1 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc1
All GPS points within IQ range
Filter the data to get only GPS-points within IQ range of NDVI_max
and NDMI_min.
IQ_ranges <- all_GPS_valid %>%
group_by(EUNISa_1) %>%
summarize(
Q1_NDVI_max = quantile(NDVI_max, 0.25, na.rm = T),
Q1_NDMI_min = quantile(NDMI_min, 0.25, na.rm = T),
Q3_NDVI_max = quantile(NDVI_max, 0.75, na.rm = T),
Q3_NDMI_min = quantile(NDMI_min, 0.75, na.rm = T),
IQR_NDVI_max = IQR(NDVI_max, na.rm = TRUE),
IQR_NDMI_min = IQR(NDMI_min, na.rm = TRUE)
)
# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
left_join(IQ_ranges, by = "EUNISa_1")
# Filter rows within the IQR range for both variables
filtered_data2 <- all_GPS_valid %>%
filter(
(NDVI_max >= Q1_NDVI_max & NDVI_max <= Q3_NDVI_max) &
(NDMI_min >= Q1_NDMI_min & NDMI_min <= Q3_NDMI_min)
) %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices2 <- sample(1:nrow(filtered_data2), 0.7 * nrow(filtered_data2))
train_data2 <- filtered_data2[train_indices2, ]
test_data2 <- filtered_data2[-train_indices2, ]
Number of points per category for filtered data:
filtered_data2 %>% count(EUNISa_1)
rf_cforest2 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data2,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest2 <- predict(rf_cforest2, newdata = test_data2,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest2, test_data2$EUNISa_1)
varimp_rf_cforest2 <- party::varimp(rf_cforest2, conditional = F)
Variable Importance Plot
varimp_rf_cforest2_df <- data.frame(Variable = names(varimp_rf_cforest2),
Importance = varimp_rf_cforest2)
ggplot(varimp_rf_cforest2_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc2 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc2
All GPS points within 1.5 * IQ range
Filter the data to get only GPS-points within 1.5 * IQ range of
NDVI_max and NDMI_min.
# Filter rows within the 1.5 * IQR range for both variables
filtered_data3 <- all_GPS_valid %>%
filter(
(NDVI_max >= (Q1_NDVI_max - 1.5 * IQR_NDVI_max) & NDVI_max <= (Q3_NDVI_max + 1.5 * IQR_NDVI_max)) &
(NDMI_min >= (Q1_NDMI_min - 1.5 * IQR_NDMI_min) & NDMI_min <= (Q3_NDMI_min + 1.5 * IQR_NDMI_min))
) %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices3 <- sample(1:nrow(filtered_data3), 0.7 * nrow(filtered_data3))
train_data3 <- filtered_data3[train_indices3, ]
test_data3 <- filtered_data3[-train_indices3, ]
Number of points per category for filtered data:
filtered_data3 %>% count(EUNISa_1)
rf_cforest3 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data3,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest3 <- predict(rf_cforest3, newdata = test_data3,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest3, test_data3$EUNISa_1)
varimp_rf_cforest3 <- party::varimp(rf_cforest3, conditional = F)
Variable Importance Plot
varimp_rf_cforest3_df <- data.frame(Variable = names(varimp_rf_cforest3),
Importance = varimp_rf_cforest3)
ggplot(varimp_rf_cforest3_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc3 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc3
All GPS points within mean +/- SD
Filter the data to get only GPS-points within mean +/- SD of NDVI_max
and NDMI_min.
mean_sd <- all_GPS_valid %>%
group_by(EUNISa_1) %>%
summarize(
mean_NDVI_max = mean(all_GPS_valid$NDVI_max, na.rm = T),
mean_NDMI_min = mean(all_GPS_valid$NDMI_min, na.rm = T),
sd_NDVI_max = sd(all_GPS_valid$NDVI_max, na.rm = T),
sd_NDMI_min = sd(all_GPS_valid$NDMI_min, na.rm = T)
)
# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
left_join(mean_sd, by = "EUNISa_1")
# Filter rows within the specified range for both variables
filtered_data4 <- all_GPS_valid %>%
filter(
(NDVI_max >= (mean_NDVI_max - sd_NDVI_max) & NDVI_max <= (mean_NDVI_max + sd_NDVI_max)) &
(NDMI_min >= (mean_NDMI_min - sd_NDMI_min) & NDMI_min <= (mean_NDMI_min + sd_NDMI_min))
) %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices4 <- sample(1:nrow(filtered_data4), 0.7 * nrow(filtered_data4))
train_data4 <- filtered_data4[train_indices4, ]
test_data4 <- filtered_data4[-train_indices4, ]
Number of points per category for filtered data:
filtered_data4 %>% count(EUNISa_1)
rf_cforest4 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data4,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest4 <- predict(rf_cforest4, newdata = test_data4,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest4, test_data4$EUNISa_1)
varimp_rf_cforest4 <- party::varimp(rf_cforest4, conditional = F)
Variable Importance Plot
varimp_rf_cforest4_df <- data.frame(Variable = names(varimp_rf_cforest4),
Importance = varimp_rf_cforest4)
ggplot(varimp_rf_cforest4_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc4 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc4
All GPS points above p20 and below p80
Filter the data to get only GPS-points above p20 and below p80 of
NDVI_max and NDMI_min.
# Filter rows above the 20th percentile and below the 80th percentile for both variables
filtered_data5 <- all_GPS_valid %>%
filter(
(NDVI_max >= percentile_20_NDVI_max & NDVI_max <= percentile_80_NDVI_max) &
(NDMI_min >= percentile_20_NDMI_min & NDMI_min <= percentile_80_NDMI_min)
) %>%
filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
!is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
!is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
!is.na(EVI_min)) %>%
mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
filter(EVI_max <= 1 & EVI_min >= -1)
Split into training and test data sets.
train_indices5 <- sample(1:nrow(filtered_data5), 0.7 * nrow(filtered_data5))
train_data5 <- filtered_data5[train_indices5, ]
test_data5 <- filtered_data5[-train_indices5, ]
Number of points per category for filtered data:
filtered_data5 %>% count(EUNISa_1)
rf_cforest5 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
NDMI_min + NDWI_max + NDWI_min + EVI_max +
EVI_min + SAVI_max + SAVI_min + canopy_height,
data = train_data5,
controls = cforest_control(
mtry = 3,
# mtry = sqrt(11)
# Default mtry = 5
# Bagging: mtry = NULL
# or = number of input variables
ntree = 500) # Default, try increasing
)
predictions_rf_cforest5 <- predict(rf_cforest5, newdata = test_data5,
OOB = TRUE, type = "response")
Confusion matrix:
confusionMatrix(predictions_rf_cforest5, test_data5$EUNISa_1)
varimp_rf_cforest5 <- party::varimp(rf_cforest5, conditional = F)
Variable Importance Plot
varimp_rf_cforest5_df <- data.frame(Variable = names(varimp_rf_cforest5),
Importance = varimp_rf_cforest5)
ggplot(varimp_rf_cforest5_df,
aes(x = reorder(Variable, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "lightblue") +
coord_flip() + theme_minimal() +
labs(title = "Variable Importance", x = "Variables", y = "Importance")
ROC curves:
# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")
# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T") # Adjust if needed
prob_df <- as.data.frame(prob_matrix)
# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)
# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))
# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
roc_obj <- roc(actual_bin[, class], prob_df[[class]])
auc_val <- round(auc(roc_obj), 3)
data.frame(
FPR = rev(roc_obj$specificities),
TPR = rev(roc_obj$sensitivities),
Class = paste0(class, " (AUC = ", auc_val, ")")
)
}) %>% bind_rows()
# Step 5: Plot ROC curves with ggplot2
roc5 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
geom_line(size = 1.2) +
geom_abline(linetype = "dashed", color = "gray") +
labs(
title = "Multiclass ROC Curves with AUC",
x = "False Positive Rate",
y = "True Positive Rate",
color = "Class (AUC)"
) +
theme_minimal() +
theme(legend.position = "bottom")
roc5
Cordillera data
AlpineGrasslands_indices <- read_csv(
"C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrassland_Sentinel_Plot_Allyear_Allmetrics.csv")
AlpineGrasslands_phen <- read_csv(
"C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
AlpineGrasslands_CH <- read_csv(
"C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_CanopyHeight_1m.csv")
VegetationTypes_indices <- read_csv(
"C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Sentinel_Plot_AllYear_Allmetrics.csv")
VegetationTypes_phen <- read_csv(
"C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
VegetationTypes_CH <- read_csv(
"C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_CanopyHeight_1m.csv")
AlpineGrasslands <- AlpineGrasslands_indices %>%
select(-`system:index`, -.geo, -Localidad) %>%
rename(Hábitat = "H�bitat") %>%
full_join(AlpineGrasslands_phen %>%
select(-`system:index`, -.geo, -Localidad) %>%
rename(Hábitat = "H�bitat")) %>%
full_join(AlpineGrasslands_CH %>%
select(-`system:index`, -.geo, -Localidad)) %>%
select(-Date__year, - `Precisi�n`) %>%
mutate(DATE = ymd(DATE)) %>%
rename(ID = "Releve_num") %>%
mutate(ID = as.character(ID)) %>%
mutate(layer = "AlpineGrasslands")
VegetationTypes <- VegetationTypes_indices %>%
select(-`system:index`, -.geo) %>%
full_join(VegetationTypes_phen %>%
select(-`system:index`, -.geo)) %>%
full_join(VegetationTypes_CH %>%
select(-`system:index`, -.geo)) %>%
rename(Hábitat = "TYPE") %>%
mutate(layer = "VegetationTypes")
Merge both datasets:
cordillera <- bind_rows(
AlpineGrasslands %>% select(DATE, ID, starts_with("NDMI"),
starts_with("NDVI"), Hábitat, "EOS_DOY",
"Peak_DOY", "SOS_DOY", "Season_Length",
"canopy_height", "layer"),
VegetationTypes %>% select(DATE, ID, starts_with("NDMI"),
starts_with("NDVI"), Hábitat, "EOS_DOY",
"Peak_DOY", "SOS_DOY", "Season_Length",
"canopy_height", "layer")
) %>%
mutate(EUNISa_1 = case_when(
Hábitat = str_detect(Hábitat, "Pastizal|Cervunal|grassland|meadow") ~ "R",
Hábitat = str_detect(Hábitat, "forest") ~ "T",
Hábitat = str_detect(Hábitat, "Scrub|scrub|Shrubland|shrubland|shrub|Heathland") ~ "S",
Hábitat = str_detect(Hábitat, "Suelo|Scree|scree|cliff") ~ "U",
Hábitat = is.na(Hábitat) ~ "R",
TRUE ~ NA_character_),
EUNISa_1_descr = case_when(
EUNISa_1 == "R" ~ "Grasslands",
EUNISa_1 == "T" ~ "Forests and other wooded land",
EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
EUNISa_1 == "U" ~ "Inland habitats with no or little soil")
)
NDVI, NDMI
distr_plot(cordillera,
c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"),
c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))
distr_plot(cordillera,
c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"),
c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))
LS0tDQp0aXRsZTogIlNjcmlwdCB0byB2YWxpZGF0ZSBwb2ludHMgaW4gUmVTdXJ2ZXkgZGF0YWJhc2UgdXNpbmcgUlMgZGF0YSINCnN1YnRpdGxlOiAiQWRkaW5nIEFUTF9CRU5FTFVYIGFuZCBDT05fTk9SRElDIFMyIGRhdGEsIHNvbWUgTGFuZHNhdCBkYXRhLCBhZGRpbmcgY2Fub3B5IGhlaWdodCBkYXRhIg0KYXV0aG9yOiAiQWxpY2lhIFZhbGTDqXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UpDQpgYGANCg0KVGhpcyBSIHNjcmlwdCBpcyB1c2VkIHRvIHZhbGlkYXRlIHRoZSBwb2ludHMgaW4gdGhlIFJlU3VydmV5IGRhdGFiYXNlIHVzaW5nIFJTIGluZGljYXRvcnMgKE5EVkksIE5ETUksIGNhbm9weSBoZWlnaHQpLg0KDQojIExvYWQgbGlicmFyaWVzDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShybmF0dXJhbGVhcnRoKQ0KbGlicmFyeShkdHBseXIpDQpsaWJyYXJ5KGxtZTQpDQpsaWJyYXJ5KGxtZXJUZXN0KQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KGdnZWZmZWN0cykNCmxpYnJhcnkocGFydHkpDQpsaWJyYXJ5KHBhcnR5a2l0KQ0KbGlicmFyeShzdHJ1Y2NoYW5nZSkNCmxpYnJhcnkoZ2dwYXJ0eSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KG1vcmVwYXJ0eSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShwUk9DKQ0KYGBgDQoNCiMgRGVmaW5lIHByaW50YWxsIGZ1bmN0aW9uDQoNCmBgYHtyfQ0KcHJpbnRhbGwgPC0gZnVuY3Rpb24odGliYmxlKSB7DQogIHByaW50KHRpYmJsZSwgd2lkdGggPSBJbmYpDQogIH0NCmBgYA0KDQojIExvYWQgZ2VvbV9mbGF0X3Zpb2xpbiBwbG90DQoNCmBgYHtyfQ0Kc291cmNlKCJodHRwczovL2dpc3QuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Jlbm1hcndpY2svMmExYmIwMTMzZmY1NjhjYmUyOGQvcmF3L2ZiNTNiZDk3MTIxZjdmOWNlOTQ3ODM3ZWYxYTRjNjVhNzNiZmZiM2YvZ2VvbV9mbGF0X3Zpb2xpbi5SIikNCmBgYA0KDQojIFJlYWQgUmVTdXJ2ZXkgZGF0YSB3aXRoIFJTIGluZGljYXRvcnMNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlM8LXJlYWRfdHN2KA0KICBoZXJlKCJkYXRhIiwgImNsZWFuIiwgImRiX3Jlc3Vydl9SU18yMDI1MDYxMS5jc3YiKSwNCiAgY29sX3R5cGVzID0gY29scygNCiAgICAjIER5bmFtaWNhbGx5IHNwZWNpZnkgRVVOSVMgY29sdW1ucyBhcyBjaGFyYWN0ZXINCiAgICAuZGVmYXVsdCA9IGNvbF9ndWVzcygpLCAgIyBEZWZhdWx0IGd1ZXNzaW5nIGZvciBvdGhlciBjb2x1bW5zDQogICAgRVVOSVNhID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYiA9IGNvbF9jaGFyYWN0ZXIoKSwNCiAgICBFVU5JU2MgPSBjb2xfY2hhcmFjdGVyKCksDQogICAgRVVOSVNkID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY180ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTX2Fzc2lnbmF0aW9uID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY180X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpDQogICAgKQ0KICApDQpgYGANCg0KTm8gcGFyc2luZyBpc3N1ZXMhDQoNCiMgU29tZSBkYXRhIG1hbmFnZW5lbXQNCg0KIyMgU2V2ZXJhbCBFVU5JUyBsZXZlbCAxIGFzc2lnbmVkDQoNCk51bWJlciBvZiByb3dzIHdoZXJlIHRoZXJlIGlzIG1vcmUgdGhhbiBvbmUgRVVOSVMgMSBhc3NpZ25lZCwgYW5kIHRoZXkgYXJlIGRpZmZlcmVudCBhbW9uZyB0aGVtLiBTZWUgd2hhdCB0byBkbyB3aXRoIHRoZXNlIGxhdGVyISBTbyBmYXIgSSB0YWtlIEVVTklTYV8xLg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTICU+JSANCiAgICAgICAjIFJvd3Mgd2l0aCBtb3JlIHRoYW4gb25lIEVVTklTIDEgYXNzaWduZWQNCiAgICAgICBmaWx0ZXIoIWlzLm5hKEVVTklTYl8xKSkgJT4lIA0KICAgICAgIGZpbHRlcihFVU5JU2FfMSE9RVVOSVNiXzEgfCBFVU5JU2JfMSAhPSBFVU5JU2NfMSB8IEVVTklTYV8xICE9IEVVTklTY18xKSkNCmBgYA0KDQpTZWUgImNvbmZ1c2lvbnMiOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SUyAlPiUgDQogICMgUm93cyB3aXRoIG1vcmUgdGhhbiBvbmUgRVVOSVMgMSBhc3NpZ25lZA0KICBmaWx0ZXIoIWlzLm5hKEVVTklTYl8xKSkgJT4lIA0KICBmaWx0ZXIoRVVOSVNhXzEhPUVVTklTYl8xIHwgRVVOSVNiXzEgIT0gRVVOSVNjXzEgfCBFVU5JU2FfMSAhPSBFVU5JU2NfMSkgJT4lDQogIGRpc3RpbmN0KEVVTklTYV8xLCBFVU5JU2JfMSwgRVVOSVNjXzEsIEVVTklTZF8xKQ0KYGBgDQoNCkRlZmluZSAiY29uZnVzaW9uIiBjb2x1bW5zOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SUyA8LSBkYl9yZXN1cnZfUlMgJT4lDQogIG11dGF0ZShFVU5JUzFfY29uZl90eXBlID0gY2FzZV93aGVuKA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJTIiAmIEVVTklTYl8xID09ICJUIiB+ICJTL1QiLA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJSIiAmIEVVTklTY18xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJSIiAmIEVVTklTY18xID09ICJTIiAmIEVVTklTZF8xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJQIiAmIEVVTklTYl8xID09ICJRIiB+ICJQL1EiLA0KICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICBFVU5JUzFfY29uZiA9ICFpcy5uYShFVU5JUzFfY29uZl90eXBlKSkNCmBgYA0KDQojIyBUaWJibGUgd2l0aCBzZWxlY3RlZCBjb2x1bW5zDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0IDwtIGRiX3Jlc3Vydl9SUyAlPiUNCiAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBDb3VudHJ5LCBSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCwNCiAgICAgICAgIGBNYW5pcHVsYXRlICh5L24pYCwgYFR5cGUgb2YgbWFuaXB1bGF0aW9uYCwgTG9uX3VwZGF0ZWQsIExhdF91cGRhdGVkLA0KICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAsIGBMb2NhdGlvbiB1bmNlcnRhaW50eSAobSlgLCBFVU5JU2FfMSwNCiAgICAgICAgIEVVTklTYV8xX2Rlc2NyLCBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIEVVTklTYV8zLCBFVU5JU2FfM19kZXNjciwNCiAgICAgICAgIEVVTklTYV80LCBFVU5JU2FfNF9kZXNjciwgRVVOSVMxX2NvbmYsIEVVTklTMV9jb25mX3R5cGUsDQogICAgICAgICBkYXRlLCB5ZWFyLCBiaW9nZW8sIHVuaXQsIHllYXJfUlMsIExvbl9SUywgTGF0X1JTLCANCiAgICAgICAgIHN0YXJ0c193aXRoKCJORFZJIiksIHN0YXJ0c193aXRoKCJORE1JIiksIHN0YXJ0c193aXRoKCJORFdJIiksDQogICAgICAgICBzdGFydHNfd2l0aCgiRVZJIiksIHN0YXJ0c193aXRoKCJTQVZJIiksIGNhbm9weV9oZWlnaHQsDQogICAgICAgICAjIFNPU19ET1ksIFNPU19kYXRlLCBORFZJX2F0X1NPUywgUGVha19ET1ksIFBlYWtfZGF0ZSwgTkRWSV9hdF9QZWFrLA0KICAgICAgICAgIyBFT1NfRE9ZLCBFT1NfZGF0ZSwgTkRWSV9hdF9FT1MsIFNlYXNvbl9MZW5ndGgsDQogICAgICAgICBTMl9kYXRhLCBSU19kYXRhLCANCiAgICAgICAgICMgUzJfcGhlbl9kYXRhLA0KICAgICAgICAgQ0hfZGF0YSkNCmBgYA0KDQojIyBUTy1ETzogTWlzc2luZyBkYXRhIGNoZWNrcw0KDQpEbyB3aGVuIGFsbCBSUyBkYXRhIGlzIHJlYWR5IQ0KDQojIyBGbGFnIHdoZW4geWVhciBpcyBkaWZmZXJlbnQgYmV0d2VlbiBSUyBkYXRhIGFuZCBSZVN1cnZleSBkYg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydCA8LSBkYl9yZXN1cnZfUlNfc2hvcnQgJT4lDQogIG11dGF0ZSh5ZWFyX2RpZmYgPSB5ZWFyICE9IHllYXJfUlMpDQpgYGANCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgJT4lIGNvdW50KHllYXJfZGlmZikNCmBgYA0KDQpOb25lIHdpdGggZGlmZmVyZW50IHllYXIgc28gZmFyLg0KDQojIyBGbGFnIHdoZW4gY29vcmRpbmF0ZXMgYXJlIGRpZmZlcmVudCBiZXR3ZWVuIFJTIGRhdGEgYW5kIFJlU3VydmV5IGRiDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0IDwtIGRiX3Jlc3Vydl9SU19zaG9ydCAlPiUNCiAgbXV0YXRlKExvbl9kaWZmID0gY2FzZV93aGVuKExvbl91cGRhdGVkID09IExvbl9SUyB+ICJOTyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNvbWV0aW1lcyB0aGV5IGFyZSBvbmx5IHNsaWdobHkgZGlmZmVyZW50DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhYnMoTG9uX3VwZGF0ZWQgLSBMb25fUlMpIDwgMC4wMSB+ICJTTUFMTCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYShMb25fdXBkYXRlZCkgfCBpcy5uYShMb25fUlMpIH4gTkEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIkxBUkdFIiksDQogICAgICAgICBMYXRfZGlmZiA9IGNhc2Vfd2hlbihMYXRfdXBkYXRlZCA9PSBMYXRfUlMgfiAiTk8iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTb21ldGltZXMgdGhleSBhcmUgb25seSBzbGlnaGx5IGRpZmZlcmVudA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWJzKExhdF91cGRhdGVkIC0gTGF0X1JTKSA8IDAuMDEgfiAiU01BTEwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoTGF0X3VwZGF0ZWQpIHwgaXMubmEoTGF0X1JTKSB+IE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJMQVJHRSIpKQ0KYGBgDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JSBjb3VudChMb25fZGlmZikNCmRiX3Jlc3Vydl9SU19zaG9ydCAlPiUgY291bnQoTGF0X2RpZmYpDQpgYGANCg0KTm9uZSB3aXRoIGRpZmZlcmVuY2VzLg0KDQojIyBIYW5kbGUgcGxvdHMgdGhhdCBoYXZlIG1vcmUgdGhhbiBvbmUgb2JzIHBlciB5ZWFyDQoNCkFkZCBjb2x1bW4gUExPVCB0byBkYXRhIHRvIGlkZW50aWZ5IHVuaXF1ZSBwbG90czoNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCA8LSBkYl9yZXN1cnZfUlNfc2hvcnQgJT4lDQogICMgT3JpZ2luYWwgbmFtZXMgZ2l2ZSBwcm9ibGVtcywgY3JlYXRlIG5ldyB2YXJzDQogIG11dGF0ZShSU19zaXRlID0gYFJlU3VydmV5IHNpdGVgLCBSU19wbG90ID0gYFJlU3VydmV5IHBsb3RgKSAlPiUNCiAgIyBDb252ZXJ0IHRvIGRhdGEudGFibGUgZm9yIGZhc3RlciBwcm9jZXNzaW5nDQogIGxhenlfZHQoKSAlPiUNCiAgIyBHcm91cCBieSB0aGUgMyB2YXJzIHRoYXQgdW5pcXVlbHkgaWRlbnRpZnkgZWFjaCBwbG90DQogIGdyb3VwX2J5KFJTX0NPREUsIFJTX3NpdGUsIFJTX3Bsb3QpICU+JQ0KICAjIENyZWF0ZSBhIG5ldyB2YXJpYWJsZSBQTE9UIGZvciBlYWNoIGdyb3VwDQogIG11dGF0ZShQTE9UID0gLkdSUCkgJT4lDQogICMgQ29udmVydCBiYWNrIHRvIHRpYmJsZQ0KICBhc190aWJibGUoKSAlPiUNCiAgIyBSZW1vdmUgdW5uZWVkZWQgdmFycw0KICBzZWxlY3QoLVJTX3NpdGUsIC1SU19wbG90KQ0KYGBgDQoNClRoZXJlIHNob3VsZCBiZSBvbmx5IG9uZSBvYnNlcnZhdGlvbiBvZiBlYWNoIHBsb3QgcGVyIHllYXIuDQoNClBsb3RzIHdoZXJlIHRoZXJlIGlzIGF0IGxlYXN0IGEgeWVhciB3aXRoIG1vcmUgdGhhbiBvbmUgb2JzZXJ2YXRpb24sIGFuZCB3aGVyZSB0aG9zZSBvYnNlcnZhdGlvbnMgaGF2ZSBhIGRpZmZlcmVudCBFVU5JUyBhc3NpZ25lZDoNCg0KYGBge3J9DQpwbG90c190b19yZW1vdmUgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGdyb3VwX2J5KFBMT1QsIHllYXIpICU+JQ0KICBzdW1tYXJpemUoRVVOSVNhXzFfbiA9IG5fZGlzdGluY3QoRVVOSVNhXzEsIG5hLnJtID0gVFJVRSkpICU+JQ0KICB1bmdyb3VwKCkgJT4lIA0KICBmaWx0ZXIoRVVOSVNhXzFfbiA+IDEpICU+JQ0KICBkaXN0aW5jdChQTE9UKQ0KYGBgDQoNClJlbW92ZSBwbG90c190b19yZW1vdmUgZnJvbSB0aGUgZGF0YWJhc2U6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGFudGlfam9pbihwbG90c190b19yZW1vdmUsIGJ5ID0gIlBMT1QiKQ0KYGBgDQoNClBsb3RzIGFuZCB5ZWFycyB3aGVyZSB0aGVyZSBpcyBtb3JlIHRoYW4gb25lIG9ic2VydmF0aW9uOg0KDQpgYGB7cn0NCnBsb3RzX3RvX21lcmdlIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBncm91cF9ieShQTE9ULCB5ZWFyKSAlPiUNCiAgIyBQbG90cyB0aGF0IGhhdmUgbW9yZSB0aGFuIG9uZSBvYnNlcnZhdGlvbiBwZXIgeWVhcg0KICBmaWx0ZXIobigpID4gMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgZGlzdGluY3QoUExPVCkNCmBgYA0KDQpTdW1tYXJpemUgcGxvdHNfdG9fbWVyZ2U6DQoNCmBgYHtyfQ0KcGxvdHNfdG9fbWVyZ2Vfc3VtbSA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZ3JvdXBfYnkoUExPVCwgeWVhcikgJT4lDQogICMgUGxvdHMgdGhhdCBoYXZlIG1vcmUgdGhhbiBvbmUgb2JzZXJ2YXRpb24gcGVyIHllYXINCiAgZmlsdGVyKG4oKSA+IDEpICU+JQ0KICBtdXRhdGUob2JzX251bSA9IHJvd19udW1iZXIoKSkgJT4lDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSBvYnNfbnVtLA0KICAgIHZhbHVlc19mcm9tID0gYyhkYXRlLCBQbG90T2JzZXJ2YXRpb25JRCksDQogICAgbmFtZXNfcHJlZml4ID0gIm9ic18iDQogICkgJT4lDQogIGFycmFuZ2UoUExPVCkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBhY3Jvc3MoYyhDb3VudHJ5LCBSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCwNCiAgICAgICAgICAgICBgTWFuaXB1bGF0ZSAoeS9uKWAsIGBUeXBlIG9mIG1hbmlwdWxhdGlvbmAsIExvbl91cGRhdGVkLA0KICAgICAgICAgICAgIExhdF91cGRhdGVkLCBgTG9jYXRpb24gbWV0aG9kYCwgYExvY2F0aW9uIHVuY2VydGFpbnR5IChtKWAsDQogICAgICAgICAgICAgRVVOSVNhXzEsIEVVTklTYV8xX2Rlc2NyLCBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIEVVTklTYV8zLA0KICAgICAgICAgICAgIEVVTklTYV8zX2Rlc2NyLCBFVU5JU2FfNCwgRVVOSVNhXzRfZGVzY3IsIEVVTklTMV9jb25mLA0KICAgICAgICAgICAgIEVVTklTMV9jb25mX3R5cGUsIGJpb2dlbywgdW5pdCwgeWVhcl9SUywgTG9uX1JTLCBMYXRfUlMsIE5EVklfbWF4LA0KICAgICAgICAgICAgIE5EVklfbWVkaWFuLCBORFZJX21pbiwgTkRWSV9tb2RlLCBORFZJX3AxMCwgTkRWSV9wOTAsIE5ETUlfbWF4LA0KICAgICAgICAgICAgIE5ETUlfbWVkaWFuLCBORE1JX21pbiwgTkRNSV9tb2RlLCBORE1JX3AxMCwgTkRNSV9wOTAsIE5EV0lfbWF4LA0KICAgICAgICAgICAgIE5EV0lfbWVkaWFuLCBORFdJX21pbiwgTkRXSV9tb2RlLCBORFdJX3AxMCwgTkRXSV9wOTAsIEVWSV9tYXgsDQogICAgICAgICAgICAgRVZJX21lZGlhbiwgRVZJX21pbiwgRVZJX21vZGUsIEVWSV9wMTAsIEVWSV9wOTAsIFNBVklfbWF4LA0KICAgICAgICAgICAgIFNBVklfbWVkaWFuLCBTQVZJX21pbiwgU0FWSV9tb2RlLCBTQVZJX3AxMCwgU0FWSV9wOTAsDQogICAgICAgICAgICAgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgIyBTT1NfRE9ZLCBTT1NfZGF0ZSwgUGVha19ET1ksIFBlYWtfZGF0ZSwgRU9TX0RPWSwgRU9TX2RhdGUsDQogICAgICAgICAgICAgUzJfZGF0YSwgUlNfZGF0YSwgQ0hfZGF0YSwgDQogICAgICAgICAgICAgIyBTMl9waGVuX2RhdGEsDQogICAgICAgICAgICAgeWVhcl9kaWZmLA0KICAgICAgICAgICAgIExvbl9kaWZmLCBMYXRfZGlmZiksIGZpcnN0KSwNCiAgICBhY3Jvc3Moc3RhcnRzX3dpdGgoImRhdGVfb2JzXyIpLCBtaW4pLA0KICAgIGFjcm9zcyhzdGFydHNfd2l0aCgiUGxvdE9ic2VydmF0aW9uSURfb2JzXyIpLCBtaW4pDQogICAgKSAlPiUNCiAgdW5ncm91cCgpDQpgYGANCg0KUmVtb3ZlIHBsb3RzX3RvX21lcmdlIGZyb20gdGhlIGRhdGFiYXNlOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBhbnRpX2pvaW4ocGxvdHNfdG9fbWVyZ2UsIGJ5ID0gIlBMT1QiKQ0KYGBgDQoNCkFuZCBhZGQgcGxvdHNfdG9fbWVyZ2Vfc3VtbSwgd2hlcmUgZWFjaCBwbG90IGFuZCB5ZWFyIG9ubHkgaGFzIG9uZSByb3c6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgPC0gYmluZF9yb3dzKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RzX3RvX21lcmdlX3N1bW0pDQpgYGANCg0KQ2hlY2sgdGhhdCB0aGVyZSBpcyBvbmx5IG9uZSByb3cgcGVyIHBsb3QgYW5kIHBlciB5ZWFyOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBncm91cF9ieShQTE9ULCB5ZWFyKSAlPiUNCiAgIyBQbG90cyB0aGF0IGhhdmUgbW9yZSB0aGFuIG9uZSBvYnNlcnZhdGlvbiBwZXIgeWVhcg0KICBmaWx0ZXIobigpID4gMSkgDQpgYGANCg0KU28sIHRvIHN1bSB1cCB3aGF0IEkgaGF2ZSBkb25lOg0KDQotIFBsb3RzIHdoZXJlIHRoZXJlIGlzIGF0IGxlYXN0IGEgeWVhciB3aXRoIG1vcmUgdGhhbiBvbmUgb2JzZXJ2YXRpb24sIGFuZCB3aGVyZSB0aG9zZSBvYnNlcnZhdGlvbnMgaGF2ZSBhIGRpZmZlcmVudCBFVU5JUyBhc3NpZ25lZDogUGxvdHMgUkVNT1ZFRCBmcm9tIHRoZSBkYXRhDQotIFBsb3RzIHdoZXJlIHRoZXJlIGlzIG1vcmUgdGhhbiBvbmUgb2JzZXJ2YXRpb24sIGJ1dCBvYnNlcnZhdGlvbnMgaGF2ZSB0aGUgc2FtZSBFVU5JUyBhc3NpZ25lZDoga2VwdCBpbiB0aGUgZGF0YS4gTWVyZ2VkIHNvIHRoYXQgdGhlcmUgaXMgb25seSBvbmUgcm93IHBlciB5ZWFyLiBJbmZvIGFib3V0IHRoZSBkaWZmZXJlbnQgZGF0ZXMgKHdoZW4gZGlmZmVyZW50KSBpcyBrZXB0IGluIGNvbHVtbnMgZGF0ZV9vYnNfMSAtIGRhdGVfb2JzXzQwLCBhbmQgaW5mbyBhYm91dCB0aGUgZGlmZmVyZW50IFBsb3RPYnNlcnZhdGlvbklEIGlzIGtlcHQgaW4gdGhlIGNvbHVtbnMgUGxvdE9ic2VydmF0aW9uSURfb2JzXzEgLSBQbG90T2JzZXJ2YXRpb25JRF9vYnNfNDAuDQoNCiMjIyBTYXZlIHRvIGNsZWFuIGRhdGENCg0KU2F2ZSBjbGVhbiBmaWxlIGZvciBhbmFseXNlcyAodG8gYmUgdXBkYXRlZCBjb250aW51b3VzbHkgZHVlIHRvIHVwZGF0ZXMgaW4gUmVTdXJ2ZXkgZGF0YWJhc2UgYW5kIHVwZGF0ZXMgb24gUlMgZGF0YSkuDQoNCmBgYHtyfQ0Kd3JpdGVfdHN2KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULA0KICAgICAgICAgIGhlcmUoImRhdGEiLCAiY2xlYW4iLCJkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF8yMDI1MDYxMC5jc3YiKSkNCmBgYA0KDQojIERpc3RyaWJ1dGlvbnMgYWxsIGJpb3JlZ2lvbnMNCg0KYGBge3J9DQojIERlZmluZSBhIGZ1bmN0aW9uIHRvIGNyZWF0ZSBoaXN0b2dyYW1zDQpwbG90X2hpc3RvZ3JhbSA8LSBmdW5jdGlvbihkYXRhLCB4X3ZhciwgeF9sYWJlbCkgew0KICBnZ3Bsb3QoZGF0YSAlPiUNCiAgICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSwNCiAgICAgICAgIGFlcyh4ID0gISFzeW0oeF92YXIpKSkgKw0KICAgIGdlb21faGlzdG9ncmFtKGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJ3aGl0ZSIpICsNCiAgICBsYWJzKHggPSB4X2xhYmVsLCB5ID0gIkZyZXF1ZW5jeSIpICsNCiAgICB0aGVtZV9idygpDQp9DQpgYGANCg0KYGBge3J9DQojIERlZmluZSBhIGZ1bmN0aW9uIHRvIGNyZWF0ZSBwbG90cyB3aXRoIHZpb2xpbiArIGJveHBsb3QgKyBwb2ludHMNCmRpc3RyX3Bsb3QgPC0gZnVuY3Rpb24oZGF0YSwgeV92YXJzLCB5X2xhYmVscykgew0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHlfdmFycykpIHsNCiAgICB5X3ZhciA8LSB5X3ZhcnNbW2ldXQ0KICAgIHlfbGFiZWwgPC0geV9sYWJlbHNbW2ldXQ0KICAgIA0KICAgIHAgPC0gZ2dwbG90KGRhdGEgPSBkYXRhICU+JQ0KICAgICAgICAgICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSwNCiAgICAgICAgICAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gISFzeW0oeV92YXIpLCBmaWxsID0gRVVOSVNhXzFfZGVzY3IpKSArDQogICAgICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCkgKw0KICAgICAgZ2VvbV9wb2ludChhZXMoeSA9ICEhc3ltKHlfdmFyKSwgY29sb3IgPSBFVU5JU2FfMV9kZXNjciksDQogICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAxLCBhbHBoYSA9IDAuMjUpICsNCiAgICAgIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMiwgb3V0bGllci5zaGFwZSA9IE5BLCBhbHBoYSA9IDAuNSkgKw0KICAgICAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogICAgICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICAgICAgbGFicyh5ID0geV9sYWJlbCwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICAgICAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICAgICAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICAgICAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KICAgIA0KICAgIHByaW50KHApDQogIH0NCn0NCmBgYA0KDQojIyBORFZJLCBORE1JLCBORFdJLCBTQVZJIGFuZCBFVkkNCg0KUmFuZ2VzIG9mIG1pbiBhbmQgbWF4Og0KDQpgYGB7cn0NCnJhbmdlKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UJE5EVklfbWF4LCBuYS5ybSA9IFQpDQpyYW5nZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCRORE1JX21heCwgbmEucm0gPSBUKQ0KcmFuZ2UoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QkTkRXSV9tYXgsIG5hLnJtID0gVCkNCnJhbmdlKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UJFNBVklfbWF4LCBuYS5ybSA9IFQpICMgU0FWSV9tYXggPiAxIQ0KcmFuZ2UoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QkRVZJX21heCwgbmEucm0gPSBUKSAjIEVWSV9tYXggPiAxIQ0KcmFuZ2UoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QkTkRWSV9taW4sIG5hLnJtID0gVCkNCnJhbmdlKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UJE5ETUlfbWluLCBuYS5ybSA9IFQpDQpyYW5nZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCRORFdJX21pbiwgbmEucm0gPSBUKQ0KcmFuZ2UoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QkU0FWSV9taW4sIG5hLnJtID0gVCkNCnJhbmdlKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UJEVWSV9taW4sIG5hLnJtID0gVCkgIyBFVklfbWluID4gMSENCmBgYA0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihpZl9hbnkoc3RhcnRzX3dpdGgoIk5EVkkiKSwgfiAueCA+IDEpKSkNCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihpZl9hbnkoc3RhcnRzX3dpdGgoIk5ETUkiKSwgfiAueCA+IDEpKSkNCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihpZl9hbnkoc3RhcnRzX3dpdGgoIk5EV0kiKSwgfiAueCA+IDEpKSkNCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihpZl9hbnkoc3RhcnRzX3dpdGgoIlNBVkkiKSwgfiAueCA+IDEpKSkNCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihpZl9hbnkoc3RhcnRzX3dpdGgoIkVWSSIpLCB+IC54ID4gMSkpKQ0KYGBgDQoNCkhpc3RvZ3JhbXMgdG8gY2hlY2sgdGhhdCBtYXggYW5kIG1pbiB2YWx1ZXMgYXJlIG9rOg0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRWSV9tYXgiLCAiTkRWSSBtYXgiKQ0KcGxvdF9oaXN0b2dyYW0oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsICJORE1JX21heCIsICJORE1JIG1heCIpDQpwbG90X2hpc3RvZ3JhbShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgIk5EV0lfbWF4IiwgIk5EV0kgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiU0FWSV9tYXgiLCAiU0FWSSBtYXgiKQ0KcGxvdF9oaXN0b2dyYW0oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIA0KICAgICAgICAgICAgICAgICAjIFNvbWUgdmFsdWVzIHdyb25nIQ0KICAgICAgICAgICAgICAgICBmaWx0ZXIoRVZJX21heCA8PSAxKSwgIkVWSV9tYXgiLCAiRVZJIG1heCIpDQpwbG90X2hpc3RvZ3JhbShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgIk5EVklfbWluIiwgIk5EVkkgbWluIikNCnBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRNSV9taW4iLCAiTkRNSSBtaW4iKQ0KcGxvdF9oaXN0b2dyYW0oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsICJORFdJX21pbiIsICJORFdJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgIlNBVklfbWluIiwgIlNBVkkgbWluIikNCnBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSANCiAgICAgICAgICAgICAgICAgIyBTb21lIHZhbHVlcyB3cm9uZyENCiAgICAgICAgICAgICAgICAgZmlsdGVyKEVWSV9taW4gPj0gLTEgJiBFVklfbWluIDw9IDEpLCAiRVZJX21pbiIsICJFVkkgbWluIikNCmBgYA0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgICAgICBmaWx0ZXIoRVZJX21heCA+IDEpKQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSU+JQ0KICBmaWx0ZXIoRVZJX21heCA+IDEpICU+JQ0KICBjb3VudChiaW9nZW8sIHVuaXQpDQpgYGANCg0KU28gZmFyLCBkbyBub3QgdXNlIEVWSSB2YWx1ZXMgYmVjYXVzZSB0aGV5IHNlZW0gdG8gYmUgd3JvbmcuIA0KDQpEaXN0cmlidXRpb24gcGxvdHM6DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULA0KICAgICAgICAgICBjKCJORFZJX21heCIsICJORFZJX3A5MCIsICJORFZJX21pbiIsICJORFZJX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRWSSBtYXgiLCAiTkRWSSBwOTAiLCAiTkRWSSBtaW4iLCAiTkRWSSBwMTAiKSkNCmRpc3RyX3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsDQogICAgICAgICAgIGMoIk5ETUlfbWF4IiwgIk5ETUlfcDkwIiwgIk5ETUlfbWluIiwgIk5ETUlfcDEwIiksIA0KICAgICAgICAgICBjKCJORE1JIG1heCIsICJORE1JIHA5MCIsICJORE1JIG1pbiIsICJORE1JIHAxMCIpKQ0KZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgYygiTkRXSV9tYXgiLCAiTkRXSV9wOTAiLCAiTkRXSV9taW4iLCAiTkRXSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EV0kgbWF4IiwgIk5EV0kgcDkwIiwgIk5EV0kgbWluIiwgIk5EV0kgcDEwIikpDQpkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULA0KICAgICAgICAgICBjKCJTQVZJX21heCIsICJTQVZJX3A5MCIsICJTQVZJX21pbiIsICJTQVZJX3AxMCIpLCANCiAgICAgICAgICAgYygiU0FWSSBtYXgiLCAiU0FWSSBwOTAiLCAiU0FWSSBtaW4iLCAiU0FWSSBwMTAiKSkNCmBgYA0KDQpgYGB7cn0NCiMgRGVmaW5lIGEgZnVuY3Rpb24gdG8gY3JlYXRlIHBsb3RzIHdpdGggdmlvbGluICsgYm94cGxvdCArIHBvaW50cw0KIyBGYWNldHRlZCBieSBTMl9kYXRhDQpkaXN0cl9wbG90X3NlbnNvciA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhcnMsIHlfbGFiZWxzKSB7DQogIGZvciAoaSBpbiBzZXFfYWxvbmcoeV92YXJzKSkgew0KICAgIHlfdmFyIDwtIHlfdmFyc1tbaV1dDQogICAgeV9sYWJlbCA8LSB5X2xhYmVsc1tbaV1dDQogICAgDQogICAgcCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEgJT4lDQogICAgICAgICAgICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIHkgPSAhIXN5bSh5X3ZhciksIGZpbGwgPSBFVU5JU2FfMV9kZXNjcikpICsNCiAgICAgIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44KSArDQogICAgICBnZW9tX3BvaW50KGFlcyh5ID0gISFzeW0oeV92YXIpLCBjb2xvciA9IEVVTklTYV8xX2Rlc2NyKSwNCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICAgICAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogICAgICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogICAgICBsYWJzKHkgPSB5X2xhYmVsLCB4ID0gIkVVTklTIGxldmVsIDEiKSArDQogICAgICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogICAgICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogICAgICB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpICsgZmFjZXRfd3JhcCh+IFMyX2RhdGEpDQogICAgDQogICAgcHJpbnQocCkNCiAgfQ0KfQ0KYGBgDQoNCkRpc3RyaWJ1dGlvbiBwbG90cyBieSBzZW5zb3I6DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkaXN0cl9wbG90X3NlbnNvcihkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgICAgICAgIGMoIk5EVklfbWF4IiwgIk5EVklfcDkwIiwgIk5EVklfbWluIiwgIk5EVklfcDEwIiksIA0KICAgICAgICAgICAgICAgICAgYygiTkRWSSBtYXgiLCAiTkRWSSBwOTAiLCAiTkRWSSBtaW4iLCAiTkRWSSBwMTAiKSkNCmRpc3RyX3Bsb3Rfc2Vuc29yKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULA0KICAgICAgICAgICAgICAgICAgYygiTkRNSV9tYXgiLCAiTkRNSV9wOTAiLCAiTkRNSV9taW4iLCAiTkRNSV9wMTAiKSwgDQogICAgICAgICAgICAgICAgICBjKCJORE1JIG1heCIsICJORE1JIHA5MCIsICJORE1JIG1pbiIsICJORE1JIHAxMCIpKQ0KZGlzdHJfcGxvdF9zZW5zb3IoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsDQogICAgICAgICAgICAgICAgICBjKCJORFdJX21heCIsICJORFdJX3A5MCIsICJORFdJX21pbiIsICJORFdJX3AxMCIpLCANCiAgICAgICAgICAgICAgICAgIGMoIk5EV0kgbWF4IiwgIk5EV0kgcDkwIiwgIk5EV0kgbWluIiwgIk5EV0kgcDEwIikpDQpkaXN0cl9wbG90X3NlbnNvcihkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgICAgICAgIGMoIlNBVklfbWF4IiwgIlNBVklfcDkwIiwgIlNBVklfbWluIiwgIlNBVklfcDEwIiksIA0KICAgICAgICAgICAgICAgICAgYygiU0FWSSBtYXgiLCAiU0FWSSBwOTAiLCAiU0FWSSBtaW4iLCAiU0FWSSBwMTAiKSkNCmBgYA0KDQoNCiMjIENIDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgImNhbm9weV9oZWlnaHQiLCAiQ2Fub3B5IGhlaWdodCAobSkiKQ0KYGBgDQogDQojIyMgU2hvdyBoYWJpdGF0cyB3aXRoIENIIGNhdGVnb3JpZXMNCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgICAgICAgIG11dGF0ZShDSF9jYXQgPQ0KICAgICAgICAgICAgICAgICAgZmFjdG9yKA0KICAgICAgICAgICAgICAgICAgICBjYXNlX3doZW4oY2Fub3B5X2hlaWdodCA9PSAwIH4gIjAgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gMCAmIGNhbm9weV9oZWlnaHQgPD0gMSB+ICIwLTEgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gMSAmIGNhbm9weV9oZWlnaHQgPD0yIH4gIjEtMiBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbm9weV9oZWlnaHQgPiAyICYgY2Fub3B5X2hlaWdodCA8PTUgfiAiMi01IG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDUgJiBjYW5vcHlfaGVpZ2h0IDw9OCB+ICI1LTggbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gOCB+ICI+IDggbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYShjYW5vcHlfaGVpZ2h0KSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKA0KICAgICAgICAgICAgICAgICAgICAgICIwIG0iLCAiMC0xIG0iLCAiMS0yIG0iLCAiMi01IG0iLCAiNS04IG0iLCAiPiA4IG0iKSkpLA0KICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIGZpbGwgPSBDSF9jYXQpKSArDQogIGdlb21fYmFyKCkgKyB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX251bWJlcigpKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xKSArDQogIGxhYnMoeCA9ICJFVU5JUyBsZXZlbCAxIiwgZmlsbCA9ICJDYW5vcHkgaGVpZ2h0IikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44LCAwLjc1KSwNCiAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpDQpgYGANCg0KIyMjIFN0YXRzIHBlciBoYWJpdGF0IHR5cGUNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgIyBLZWVwIG9ubHkgZm9yZXN0cywgZ3Jhc3NsYW5kcywgc2hydWJsYW5kcyBhbmQgd2V0bGFuZHMNCiAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzFfZGVzY3IpICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGNhbm9weV9oZWlnaHQsIGxpc3QoDQogICAgbWVhbiA9IG1lYW4sDQogICAgbWVkaWFuID0gbWVkaWFuLA0KICAgIHNkID0gc2QsDQogICAgbWluID0gbWluLA0KICAgIG1heCA9IG1heA0KICAgICksIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KIyMgUGhlbm9sb2d5DQoNCiMjIyBDYWxjdWxhdGUgbWV0cmljcw0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBtdXRhdGUoDQogICAgIyBEaWZmZXJlbmNlIE5EVkkgYmV0d2VlbiBQZWFrIGFuZCBTT1MNCiAgICBkaWZmX1BlYWtfU09TID0gTkRWSV9hdF9QZWFrIC0gTkRWSV9hdF9TT1MsDQogICAgIyBEaWZmZXJlbmNlIE5EVkkgYmV0d2VlbiBQZWFrIGFuZCBFT1MNCiAgICBkaWZmX1BlYWtfRU9TID0gTkRWSV9hdF9QZWFrIC0gTkRWSV9hdF9FT1MpDQpgYGANCg0KIyMjIEhpc3RvZ3JhbXMgcGhlbm9sb2d5IG1lYXN1cmVzDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikgJiBTMl9waGVuX2RhdGEgPT0gVCkgJT4lDQogICAgICAgICBwaXZvdF9sb25nZXIoY29scyA9IGMoU09TX0RPWSwgUGVha19ET1ksIEVPU19ET1kpLCBuYW1lc190byA9ICJuYW1lIiwNCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSwNCiAgICAgICBhZXMoeCA9IHZhbHVlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGZhY2V0X2dyaWQoYmlvZ2VvIH4gbmFtZSwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgdGhlbWVfYncoKQ0KZ2dwbG90KGRhdGEgPSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikgJiBTMl9waGVuX2RhdGEgPT0gVCkgJT4lDQogICAgICAgICBwaXZvdF9sb25nZXIoY29scyA9IGMoTkRWSV9hdF9TT1MsIE5EVklfYXRfUGVhaywgTkRWSV9hdF9FT1MpLA0KICAgICAgICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIm5hbWUiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSwNCiAgICAgICBhZXMoeCA9IHZhbHVlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGZhY2V0X2dyaWQoYmlvZ2VvIH4gbmFtZSwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgdGhlbWVfYncoKQ0KZ2dwbG90KGRhdGEgPSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikgJiBTMl9waGVuX2RhdGEgPT0gVCkgJT4lDQogICAgICAgICBwaXZvdF9sb25nZXIoY29scyA9IGMoZGlmZl9QZWFrX1NPUywgZGlmZl9QZWFrX0VPUyksDQogICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibmFtZSIsIHZhbHVlc190byA9ICJ2YWx1ZSIpLA0KICAgICAgIGFlcyh4ID0gdmFsdWUpKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpICsNCiAgZmFjZXRfZ3JpZChiaW9nZW8gfiBuYW1lLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICB0aGVtZV9idygpDQpnZ3Bsb3QoZGF0YSA9IGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgIyBLZWVwIG9ubHkgZm9yZXN0cywgZ3Jhc3NsYW5kcywgc2hydWJsYW5kcyBhbmQgd2V0bGFuZHMNCiAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSAmIFMyX3BoZW5fZGF0YSA9PSBUKSAlPiUNCiAgICAgICAgIHBpdm90X2xvbmdlcihjb2xzID0gYyhTZWFzb25fTGVuZ3RoKSwNCiAgICAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJuYW1lIiwgdmFsdWVzX3RvID0gInZhbHVlIiksDQogICAgICAgYWVzKHggPSB2YWx1ZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBmYWNldF9ncmlkKGJpb2dlbyB+IG5hbWUsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyMgRGlzdHJpYnV0aW9ucw0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsDQogICAgICAgICAgIGMoIlNPU19ET1kiLCJQZWFrX0RPWSIsICJFT1NfRE9ZIiwNCiAgICAgICAgICAgICAiTkRWSV9hdF9TT1MiLCAiTkRWSV9hdF9QZWFrIiwgIk5EVklfYXRfRU9TIiwNCiAgICAgICAgICAgICAiZGlmZl9QZWFrX1NPUyIsImRpZmZfUGVha19FT1MiLCAiU2Vhc29uX0xlbmd0aCIpLA0KICAgICAgICAgICBjKCJTT1MgRE9ZIiwgIlBlYWsgRE9ZIiwgIkVPUyBET1kiLA0KICAgICAgICAgICAgICJORFZJIGF0IFNPUyIsICJORFZJIGF0IFBlYWsiLCAiTkRWSSBhdCBFT1MiLA0KICAgICAgICAgICAgICJEaWZmZXJlbmNlIFBlYWstU09TIiwgIkRpZmZlcmVuY2UgUGVhay1FT1MiLCAiU2Vhc29uIExlbmd0aCIpKQ0KYGBgDQoNCiMgRGlzdHJpYnV0aW9ucyBwZXIgYmlvcmVnaW9uDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjcmVhdGUgcGxvdHMgd2l0aCB2aW9saW4gKyBib3hwbG90ICsgcG9pbnRzDQpkaXN0cl9wbG90X2Jpb2dlbyA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhcnMsIHlfbGFiZWxzKSB7DQogIHBsb3RzIDwtIGxpc3QoKQ0KICANCiAgZm9yIChpIGluIHNlcV9hbG9uZyh5X3ZhcnMpKSB7DQogICAgeV92YXIgPC0geV92YXJzW1tpXV0NCiAgICB5X2xhYmVsIDwtIHlfbGFiZWxzW1tpXV0NCiAgICANCiAgICBwIDwtIGdncGxvdChkYXRhID0gZGF0YSAlPiUNCiAgICAgICAgICAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSksDQogICAgICAgICAgICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9ICEhc3ltKHlfdmFyKSwgZmlsbCA9IEVVTklTYV8xX2Rlc2NyKSkgKw0KICAgICAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgpICsNCiAgICAgIGdlb21fcG9pbnQoYWVzKHkgPSAhIXN5bSh5X3ZhciksIGNvbG9yID0gRVVOSVNhXzFfZGVzY3IpLA0KICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMSwgYWxwaGEgPSAwLjI1KSArDQogICAgICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBzaGFwZSA9IDIwLCBzaXplID0gMSkgKw0KICAgICAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgICAgIGxhYnMoeSA9IHlfbGFiZWwsIHggPSAiRVVOSVNhXzFfZGVzY3IiKSArDQogICAgICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogICAgICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogICAgICB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpICsgZmFjZXRfd3JhcCh+IGJpb2dlbykNCiAgICANCiAgICBwbG90c1tbeV92YXJdXSA8LSBwDQogIH0NCiAgDQogIHJldHVybihwbG90cykNCn0NCmBgYA0KDQojIyBORFZJLCBORE1JLCBORFdJLCBTQVZJIGFuZCBFVkkNCg0KRGlzdHJpYnV0aW9uIHBsb3RzOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgIGMoIk5EVklfbWF4IiwgIk5EVklfcDkwIiwgIk5EVklfbWluIiwgIk5EVklfcDEwIiksIA0KICAgICAgICAgICBjKCJORFZJIG1heCIsICJORFZJIHA5MCIsICJORFZJIG1pbiIsICJORFZJIHAxMCIpKQ0KZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgIGMoIk5ETUlfbWF4IiwgIk5ETUlfcDkwIiwgIk5ETUlfbWluIiwgIk5ETUlfcDEwIiksIA0KICAgICAgICAgICBjKCJORE1JIG1heCIsICJORE1JIHA5MCIsICJORE1JIG1pbiIsICJORE1JIHAxMCIpKQ0KZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgIGMoIk5EV0lfbWF4IiwgIk5EV0lfcDkwIiwgIk5EV0lfbWluIiwgIk5EV0lfcDEwIiksIA0KICAgICAgICAgICBjKCJORFdJIG1heCIsICJORFdJIHA5MCIsICJORFdJIG1pbiIsICJORFdJIHAxMCIpKQ0KZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgIGMoIlNBVklfbWF4IiwgIlNBVklfcDkwIiwgIlNBVklfbWluIiwgIlNBVklfcDEwIiksIA0KICAgICAgICAgICBjKCJTQVZJIG1heCIsICJTQVZJIHA5MCIsICJTQVZJIG1pbiIsICJTQVZJIHAxMCIpKQ0KZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcighaXMubmEoYmlvZ2VvKSkgJT4lDQogICAgICAgICAgICAgZmlsdGVyKEVWSV9tYXggPD0gMSkgJT4lDQogICAgICAgICAgICAgZmlsdGVyKEVWSV9taW4gPj0gLTEgJiBFVklfbWluIDw9IDEpLA0KICAgICAgICAgICBjKCJFVklfbWF4IiwgIkVWSV9wOTAiLCAiRVZJX21pbiIsICJFVklfcDEwIiksIA0KICAgICAgICAgICBjKCJFVkkgbWF4IiwgIkVWSSBwOTAiLCAiRVZJIG1pbiIsICJFVkkgcDEwIikpDQpgYGANCg0KIyMgQ0gNCg0KYGBge3J9DQpkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgImNhbm9weV9oZWlnaHQiLCAiQ2Fub3B5IGhlaWdodCAobSkiKQ0KYGBgDQoNCkluIHRoaXMgcGxvdCwgdGhvc2Ugd2l0aCBiaW9nZW8gPSBOQSBhcmUgdGhvc2UgdGhhdCBkbyBub3QgaGF2ZSBTMiBvciBMYW5kc2F0IGRhdGEgKGFuZCB0aHVzIGJpb2dlbyBoYXMgbm90IGJlZW4gYXNzaWduZWQpLCBidXQgaGF2ZSBDSCBkYXRhLiBXZSBzaG91bGQgbGF0ZXIgYXNzaWduIGEgYmlvZ2VvIGJhc2VkIG9uIGxvY2F0aW9uLiANCg0KIyMgUGhlbm9sb2d5DQoNCmBgYHtyfQ0KZGlzdHJfcGxvdF9iaW9nZW8oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcighaXMubmEoYmlvZ2VvKSksDQogICAgICAgICAgICAgICAgICBjKCJTT1NfRE9ZIiwgIlBlYWtfRE9ZIiwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICAgICAgICAiTkRWSV9hdF9TT1MiLCAiTkRWSV9hdF9QZWFrIiwgIk5EVklfYXRfRU9TIiwNCiAgICAgICAgICAgICAgICAgICAgImRpZmZfUGVha19TT1MiLCAiZGlmZl9QZWFrX0VPUyIsICJTZWFzb25fTGVuZ3RoIiksDQogICAgICAgICAgICAgICAgICBjKCJTT1MgRE9ZIiwgIlBlYWsgRE9ZIiwgIkVPUyBET1kiLA0KICAgICAgICAgICAgICAgICAgICAiTkRWSSBhdCBTT1MiLCAiTkRWSSBhdCBQZWFrIiwgIk5EVkkgYXQgRU9TIiwNCiAgICAgICAgICAgICAgICAgICAgIkRpZmZlcmVuY2UgUGVhay1TT1MiLCAiRGlmZmVyZW5jZSBQZWFrLUVPUyIsDQogICAgICAgICAgICAgICAgICAgICJTZWFzb24gTGVuZ3RoIikpDQpgYGANCg0KQk9SIG1pc3NpbmcgYmVjYXVzZSB0aGVyZSBpcyBubyBwaGVub2xvZ3kgaW5mbyBmb3IgRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikuDQoNCiMgSEVSRTogVmVyaWZ5IFNPUy1QZWFrX0VPUyBPRFkNCg0KRVJST1JTISBCZWEgaXMgY2hlY2tpbmcgdGhpczoNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKFNPU19ET1kgPiBQZWFrX0RPWSkNCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSBmaWx0ZXIoUGVha19ET1kgPiBFT1NfRE9ZKQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihTT1NfRE9ZID4gRU9TX0RPWSkNCmBgYA0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSBmaWx0ZXIoTkRWSV9hdF9QZWFrIDwgTkRWSV9hdF9TT1MpDQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKE5EVklfYXRfUGVhayA8IE5EVklfYXRfRU9TKQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihORFZJX2F0X1BlYWsgPCBORFZJX21heCkNCmBgYA0KDQojIEluY2x1ZGluZyBQTE9UIChVU0UgTEFURVI/KQ0KDQpTdW1tYXJpemUgdmFyaWFibGVzIGJ5IHBsb3Q6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1Rfc3VtbSA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUKSAlPiUNCiAgZ3JvdXBfYnkoUExPVCkgJT4lDQogIHN1bW1hcml6ZShFVU5JUzEgPSBpZl9lbHNlKG5fZGlzdGluY3QoRVVOSVNhXzEpID4gMSwgIkNoYW5nZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuaXF1ZShFVU5JU2FfMSlbMV0pLA0KICAgICAgICAgICAgY291bnQgPSBuKCksDQogICAgICAgICAgICBhY3Jvc3Moc3RhcnRzX3dpdGgoIk5EVkkiKSwgbGlzdChtZWFuID0gbWVhbiwgc2QgPSBzZCksDQogICAgICAgICAgICAgICAgICAgLm5hbWVzID0gIntjb2x9X3tmbn0iKSkNCg0KYGBgDQoNCk1heWJlIHVzZSBsYXRlciBiZWNhdXNlIG5vdyBtYW55IHBsb3RzIGhhdmUgb25seSBvbmUgb2JzZXJ2YXRpb24sIHByb2JhYmx5IGJlY2F1c2Ugc29tZSBMYW5kc2F0IGRhdGEgaXMgbWlzc2luZz8NCg0KIyBGaXJzdCB2YWxpZGF0aW9uDQoNCkZvciBULCBSLCBTLCBRIGhhYml0YXRzLg0KDQpEZWZpbmUgYSBzZXQgb2YgcnVsZXMgZm9yIGEgZmlyc3QgdmFsaWRhdGlvbiBvZiBBTEwgUmVTdXJ2ZXkgZGF0YS4gV2UgY2FuIGNhbGwgdGhlc2UgIkV4cGVydC1iYXNlZCIgcnVsZXMuDQoNCk51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gUmVTdXJ2ZXkgZnJvbSB0aGUgaGFiaXRhdHMgb2YgaW50ZXJlc3Q6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpKQ0KYGBgDQoNCk51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gUmVTdXJ2ZXkgZnJvbSB0aGUgaGFiaXRhdHMgb2YgaW50ZXJlc3QgYW5kIHdpdGggYWxsIFJTIGRhdGE6DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgIGZpbHRlcihDSF9kYXRhID09IFQpICU+JQ0KICAgICAgIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT1UKSAlPiUNCiAgICAgICBmaWx0ZXIoUzJfcGhlbl9kYXRhID09IFQpKQ0KYGBgDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkNCmBgYA0KDQojIyBEZWZpbmUgcnVsZXMNCg0KQ3JlYXRlIGNvbHVtbiBmb3IgZmlyc3QgdmFsaWRhdGlvbiBiYXNlZCBvbiBkaWZmZXJlbnQgaW5kaWNhdG9ycywgd2hlcmUgIndyb25nIiBpcyBub3RlZCB3aGVuIHRoZSB2YWxpZGF0aW9uIHJ1bGUgaXMgbm90IG1ldC4gSW5jbHVkZSBFVU5JUzEgY29uZnVzaW9ucy4NCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUgY291bnQoRVVOSVNhXzEsIEVVTklTMV9jb25mX3R5cGUpDQpgYGANCg0KRGVmaW5lIHJ1bGVzOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsIDwtDQogIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICBtdXRhdGUoDQogICAgdmFsaWRfMV9ORFdJID0gY2FzZV93aGVuKA0KICAgICAgIyBQb2ludHMgdGhhdCBhcmUgYmFzaWNhbGx5IHdhdGVyDQogICAgICBORFdJX21heCA+IDAuMyB+ICJ3cm9uZyIsDQogICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXyksDQogICAgdmFsaWRfMV9DSCA9IGNhc2Vfd2hlbigNCiAgICAgICMgVCBwb2ludHMgd2l0aCBsb3cgQ0gNCiAgICAgIEVVTklTYV8xID09ICJUIiAmIGNhbm9weV9oZWlnaHQgPCA4IH4gIndyb25nIiwNCiAgICAgICMgUyBwb2ludHMgd2l0aCBsb3cgQ0gNCiAgICAgIEVVTklTYV8xID09IlMiICYgY2Fub3B5X2hlaWdodCA8IDUgfiAid3JvbmciLA0KICAgICAgIyBSICYgUSBwb2ludHMgd2l0aCBoaWdoIENIDQogICAgICBFVU5JU2FfMSAlaW4lIGMoIlIiLCAiUSIpICYgY2Fub3B5X2hlaWdodCA+IDIgfiAid3JvbmciLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgIHZhbGlkXzFfTkRWSSA9IGNhc2Vfd2hlbigNCiAgICAgICMgVCBwb2ludHMgd2l0aCBsb3cgTkRWSV9tYXgNCiAgICAgIEVVTklTYV8xID09ICJUIiAmIE5EVklfbWF4IDwgMC42IH4gIndyb25nIiwNCiAgICAgICMgUy1SLVEgcG9pbnRzIHdpdGggbG93IE5EVklfbWF4DQogICAgICBFVU5JU2FfMSAlaW4lIGMoIlIiLCAiUyIsICJRIikgJiBORFZJX21heCA8IDAuMiB+ICJ3cm9uZyIsDQogICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXyksDQogICAgIyBDb3VudCBob3cgbWFueSB2YWxpZGF0aW9uIHJ1bGVzIGFyZSBub3QgbWV0DQogICAgdmFsaWRfMV9jb3VudCA9IHJvd1N1bXMoYWNyb3NzKGModmFsaWRfMV9ORFdJLCB2YWxpZF8xX0NILCB2YWxpZF8xX05EVkkpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAuID09ICJ3cm9uZyIpLCBuYS5ybSA9IFRSVUUpLA0KICAgICMgUG9pbnRzIHdoZXJlIGF0IGxlYXN0IDEgcnVsZSBub3QgbWV0DQogICAgdmFsaWRfMSA9IGlmX2Vsc2UodmFsaWRfMV9jb3VudCA+IDAsICJBdCBsZWFzdCAxIHJ1bGUgYnJva2VuIiwNCiAgICAgICAgICAgICAgICAgICAgICAiTm8gcnVsZXMgYnJva2VuIHNvIGZhciIpDQogICAgKQ0KYGBgDQoNCiMjIFBsb3RzIGZpcnN0IHZhbGlkYXRpb24NCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwlPiUNCiAgICAgICAgIG11dGF0ZShydWxlc19icm9rZW4gPSBjYXNlX3doZW4oDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciIH4gIk5EV0kiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX05EVkkgPT0gIndyb25nIiB+ICJORFZJIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9DSCA9PSAid3JvbmciIH4gIkNIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAyICYNCiAgICAgICAgICAgICB2YWxpZF8xX05EV0kgPT0gIndyb25nIiAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmcifiAiTkRXSSArIE5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRXSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAyICYNCiAgICAgICAgICAgICB2YWxpZF8xX05EVkkgPT0gIndyb25nIiAmIHZhbGlkXzFfQ0ggPT0gIndyb25nIn4gIk5EVkkgKyBDSCIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMyB+ICJORFdJICsgTkRWSSArIENIIiwNCiAgICAgICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgICAgICAgICkpLCANCiAgICAgICBhZXMoeCA9IHZhbGlkXzFfY291bnQsIGZpbGwgPSBydWxlc19icm9rZW4pKSArDQogIGdlb21fYmFyKCkgKyBsYWJzKHggPSAiTnVtYmVyIG9mIGJyb2tlbiBydWxlcyIpDQpgYGANCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShydWxlc19icm9rZW4gPSBjYXNlX3doZW4oDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciIH4gIk5EV0kiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX05EVkkgPT0gIndyb25nIiB+ICJORFZJIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9DSCA9PSAid3JvbmciIH4gIkNIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAyICYNCiAgICAgICAgICAgICB2YWxpZF8xX05EV0kgPT0gIndyb25nIiAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmcifiAiTkRXSSArIE5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRXSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAyICYNCiAgICAgICAgICAgICB2YWxpZF8xX05EVkkgPT0gIndyb25nIiAmIHZhbGlkXzFfQ0ggPT0gIndyb25nIn4gIk5EVkkgKyBDSCIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMyB+ICJORFdJICsgTkRWSSArIENIIiwNCiAgICAgICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgICAgICAgICkpICU+JQ0KICBjb3VudChydWxlc19icm9rZW4sIEVVTklTMV9jb25mX3R5cGUpDQpgYGANCg0KUHJvcG9ydGlvbiBvZiBvYnNlcnZhdGlvbnMgbm90IHZhbGlkYXRlZCAoc28gZmFyKToNCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JSBmaWx0ZXIodmFsaWRfMV9jb3VudCA+IDApKS8NCiAgbnJvdyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCkNCmBgYA0KDQpCdXQgYmUgYXdhcmUgdGhhdCB0aGVyZSBhcmUgc3RpbGwgTUFOWSBtaXNzaW5nIFJTIGRhdGEuDQoNCmBgYHtyfQ0KZ2dwbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICAgICAgICAgbXV0YXRlKGRpZmZfR1BTID0gaWZfZWxzZSgNCiAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgIT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgfA0KICAgICAgICAgICAgIGlzLm5hKGBMb2NhdGlvbiBtZXRob2RgKSwgIm5vIiwgInllcyIpKSwgDQogICAgICAgYWVzKHggPSBkaWZmX0dQUywgZmlsbCA9IHZhbGlkXzEpKSArDQogIGdlb21fYmFyKCkgKyBsYWJzKHggPSAiRGlmZmVyZW50aWFsIEdQUyIpDQpnZ3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQogICAgICAgICBtdXRhdGUoR1BTID0gY2FzZV93aGVuKA0KICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB+ICJ5ZXMiLA0KICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiIH4gInllcyIsDQogICAgICAgICAgIGlzLm5hKGBMb2NhdGlvbiBtZXRob2RgKSB+ICJubyIsDQogICAgICAgICAgIFRSVUUgfiAibm8iDQogICAgICAgICApKSwgDQogICAgICAgYWVzKHggPSBHUFMsIGZpbGwgPSB2YWxpZF8xKSkgKw0KICBnZW9tX2JhcigpICsgbGFicyh4ID0gIkdQUyIpDQpgYGANCg0KUG9pbnRzIHdpdGggYW55IHJ1bGUgYnJva2VuIGFuZCBjb25mdXNpb24gYmV0d2VlbiBFVU5JUzoNCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICAgICAgIGZpbHRlcihFVU5JUzFfY29uZiA9PSBUICYgdmFsaWRfMV9jb3VudCA+IDApKQ0KYGBgDQoNCkNvbnZlcnQgdG8gc2hwIHRvIGxvb2sgYXQgdGhlc2UgaW4gR0lTOg0KDQpgYGB7cn0NCiMgc3Rfd3JpdGUoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQojICAgICAgICAgICAgZmlsdGVyKEVVTklTMV9jb25mID09IFQgJiB2YWxpZF8xX2NvdW50ID4gMCkgJT4lDQojICAgICAgICAgICAgc3RfYXNfc2YoY29vcmRzID0gYygiTG9uX3VwZGF0ZWQiLCAiTGF0X3VwZGF0ZWQiKSwgY3JzID0gNDMyNiksDQojICAgICAgICAgICJDOi9HSVMvTU9USVZBVEUvc2hhcGVmaWxlcy9yZXN1cnZfbm90X3ZhbF9FVU5JU19jb25mLnNocCIpDQpgYGANCg0KQ2hlY2tlZCBhbmQgeWVzDQoNCkhvdyBtYW55IHBvaW50cyB3aXRoIGRpZmZlcmVudGlhbCBHUFMgdGhhdCBoYXZlIGF0IGxlYXN0IDEgcnVsZSBicm9rZW4/DQoNCmBgYHtyfQ0KbnJvdyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiAgZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiICYNCiAgICAgICAgICAgdmFsaWRfMSA9PSAiQXQgbGVhc3QgMSBydWxlIGJyb2tlbiIpKQ0KYGBgDQoNCkNvbnZlcnQgdG8gc2hwIHRvIGxvb2sgYXQgdGhlc2UgaW4gR0lTOg0KDQpgYGB7cn0NCiMgc3Rfd3JpdGUoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQojICAgICAgICAgICAgZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiICYNCiMgICAgICAgICAgICAgICAgICAgICB2YWxpZF8xID09ICJBdCBsZWFzdCAxIHJ1bGUgYnJva2VuIikgJT4lDQojICAgICAgICAgICAgc3RfYXNfc2YoY29vcmRzID0gYygiTG9uX3VwZGF0ZWQiLCAiTGF0X3VwZGF0ZWQiKSwgY3JzID0gNDMyNiksDQojICAgICAgICAgICJDOi9HSVMvTU9USVZBVEUvc2hhcGVmaWxlcy9yZXN1cnZfbm90X3ZhbF9kaWZmX0dQUy5zaHAiKQ0KYGBgDQoNCiMgTWFwcw0KDQojIyBQb2ludHMgR1BTDQoNCmBgYHtyfQ0KIyBMb2FkIHdvcmxkIGJvdW5kYXJpZXMNCndvcmxkIDwtIG5lX2NvdW50cmllcyhzY2FsZSA9ICJtZWRpdW0iLCByZXR1cm5jbGFzcyA9ICJzZiIpDQoNCiMgQ2FsY3VsYXRlIHRoZSBleHRlbnQgb2YgdGhlIHBvaW50cw0KcG9pbnRzX0dQU19leHRlbnQgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICBmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgfA0KICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiKSAlPiUNCiAgc3VtbWFyaXNlKGxvbl9taW4gPSBtaW4oTG9uX3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsb25fbWF4ID0gbWF4KExvbl91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21pbiA9IG1pbihMYXRfdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxhdF9tYXggPSBtYXgoTGF0X3VwZGF0ZWQsIG5hLnJtID0gVFJVRSkpDQoNCiMgQWRkIHBhZGRpbmcgdG8gdGhlIGV4dGVudCAoYWRqdXN0IGFzIG5lZWRlZCkNCnBhZGRpbmcgPC0gMiAgIyBBZGp1c3QgcGFkZGluZyB0byB5b3VyIHByZWZlcmVuY2UNCnhfbGltaXRzIDwtIGMocG9pbnRzX0dQU19leHRlbnQkbG9uX21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19HUFNfZXh0ZW50JGxvbl9tYXggKyBwYWRkaW5nKQ0KeV9saW1pdHMgPC0gYyhwb2ludHNfR1BTX2V4dGVudCRsYXRfbWluIC0gcGFkZGluZywNCiAgICAgICAgICAgICAgcG9pbnRzX0dQU19leHRlbnQkbGF0X21heCArIHBhZGRpbmcpDQoNCiMgQ3JlYXRlIHRoZSB6b29tZWQgbWFwDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHdvcmxkLCBmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImdyYXkiKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgICAgICAgICAgICAgIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICAgICAgICAgICAgICAgZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwNCiAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIiksDQogICAgICAgICAgICAgYWVzKHggPSBMb25fdXBkYXRlZCwgeSA9IExhdF91cGRhdGVkLCBjb2xvciA9IEVVTklTYV8xKSwNCiAgICAgICAgICAgICBzaXplID0gMSkgKw0KICBjb29yZF9zZih4bGltID0geF9saW1pdHMsIHlsaW0gPSB5X2xpbWl0cykgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpOdW1iZXIgb2YgR1BTIHBvaW50cyBieSBDb3VudHJ5Og0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwNCiAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIikgJT4lDQogIGNvdW50KENvdW50cnkpDQpgYGANCg0KIyMgUG9pbnRzIFJlU3VydmV5DQoNCmBgYHtyfQ0KIyBDYWxjdWxhdGUgdGhlIGV4dGVudCBvZiB0aGUgcG9pbnRzDQpwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50IDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgc3VtbWFyaXNlKGxvbl9taW4gPSBtaW4oTG9uX3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsb25fbWF4ID0gbWF4KExvbl91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21pbiA9IG1pbihMYXRfdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxhdF9tYXggPSBtYXgoTGF0X3VwZGF0ZWQsIG5hLnJtID0gVFJVRSkpDQoNCiMgQWRkIHBhZGRpbmcgdG8gdGhlIGV4dGVudCAoYWRqdXN0IGFzIG5lZWRlZCkNCnBhZGRpbmcgPC0gMiAgIyBBZGp1c3QgcGFkZGluZyB0byB5b3VyIHByZWZlcmVuY2UNCnhfbGltaXRzIDwtIGMocG9pbnRzX3Jlc3VydmV5X2V4dGVudCRsb25fbWluIC0gcGFkZGluZywNCiAgICAgICAgICAgICAgcG9pbnRzX3Jlc3VydmV5X2V4dGVudCRsb25fbWF4ICsgcGFkZGluZykNCnlfbGltaXRzIDwtIGMocG9pbnRzX3Jlc3VydmV5X2V4dGVudCRsYXRfbWluIC0gcGFkZGluZywNCiAgICAgICAgICAgICAgcG9pbnRzX3Jlc3VydmV5X2V4dGVudCRsYXRfbWF4ICsgcGFkZGluZykNCg0KIyBDcmVhdGUgdGhlIHpvb21lZCBtYXANCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gd29ybGQsIGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgICAgICAgICAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICksDQogICAgICAgICAgICAgYWVzKHggPSBMb25fdXBkYXRlZCwgeSA9IExhdF91cGRhdGVkLCBjb2xvciA9IEVVTklTYV8xKSwNCiAgICAgICAgICAgICBzaXplID0gMSkgKw0KICBjb29yZF9zZih4bGltID0geF9saW1pdHMsIHlsaW0gPSB5X2xpbWl0cykgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpOdW1iZXIgb2YgUmVTdXJ2ZXkgcG9pbnRzIGJ5IENvdW50cnk6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICBjb3VudChDb3VudHJ5KQ0KYGBgDQoNCiMgRGlzdHJpYnV0aW9ucyBmcm9tIEdQUyBwb2ludHMgd2l0aG91dCBydWxlcyBicm9rZW4gc28gZmFyDQoNCkNyZWF0ZSB0aWJibGUgd2l0aCBkaWZmZXJlbnRpYWwgR1BTIHBvaW50cyB3aXRob3V0IHJ1bGVzIGJyb2tlbiBzbyBmYXI6DQoNCmBgYHtyfQ0KYWxsX0dQU192YWxpZCA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiAgZmlsdGVyKChgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8IA0KICAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIiApICYNCiAgICAgICAgICAgdmFsaWRfMSA9PSAiTm8gcnVsZXMgYnJva2VuIHNvIGZhciIpIA0KYGBgDQoNCiMjIE5EVkksIE5ETUksIE5EV0ksIFNBVkkgYW5kIEVWSQ0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiTkRWSV9wOTAiLCAiTkRWSV9taW4iLCAiTkRWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EVkkgbWF4IiwgIk5EVkkgcDkwIiwgIk5EVkkgbWluIiwgIk5EVkkgcDEwIikpDQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgIGMoIk5ETUlfbWF4IiwgIk5ETUlfcDkwIiwgIk5ETUlfbWluIiwgIk5ETUlfcDEwIiksIA0KICAgICAgICAgICBjKCJORE1JIG1heCIsICJORE1JIHA5MCIsICJORE1JIG1pbiIsICJORE1JIHAxMCIpKQ0KZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICBjKCJORFdJX21heCIsICJORFdJX3A5MCIsICJORFdJX21pbiIsICJORFdJX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRXSSBtYXgiLCAiTkRXSSBwOTAiLCAiTkRXSSBtaW4iLCAiTkRXSSBwMTAiKSkNCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiU0FWSV9tYXgiLCAiU0FWSV9wOTAiLCAiU0FWSV9taW4iLCAiU0FWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIlNBVkkgbWF4IiwgIlNBVkkgcDkwIiwgIlNBVkkgbWluIiwgIlNBVkkgcDEwIikpDQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQgJT4lDQogICAgICAgICAgICAgZmlsdGVyKEVWSV9tYXggPD0gMSkgJT4lDQogICAgICAgICAgICAgZmlsdGVyKEVWSV9taW4gPj0gLTEgJiBFVklfbWluIDw9IDEpLA0KICAgICAgICAgICBjKCJFVklfbWF4IiwgIkVWSV9wOTAiLCAiRVZJX21pbiIsICJFVklfcDEwIiksIA0KICAgICAgICAgICBjKCJFVkkgbWF4IiwgIkVWSSBwOTAiLCAiRVZJIG1pbiIsICJFVkkgcDEwIikpDQpgYGANCg0KIyMgQ0gNCg0KYGBge3J9DQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsICJjYW5vcHlfaGVpZ2h0IiwgIkNhbm9weSBoZWlnaHQgKG0pIikNCmBgYA0KDQojIyBQaGVub2xvZ3kNCg0KYGBge3J9DQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgIGMoIlNPU19ET1kiLCJQZWFrX0RPWSIsICJFT1NfRE9ZIiwNCiAgICAgICAgICAgICAiTkRWSV9hdF9TT1MiLCAiTkRWSV9hdF9QZWFrIiwgIk5EVklfYXRfRU9TIiwNCiAgICAgICAgICAgICAiZGlmZl9QZWFrX1NPUyIsImRpZmZfUGVha19FT1MiLCAiU2Vhc29uX0xlbmd0aCIpLA0KICAgICAgICAgICBjKCJTT1MgRE9ZIiwgIlBlYWsgRE9ZIiwgIkVPUyBET1kiLA0KICAgICAgICAgICAgICJORFZJIGF0IFNPUyIsICJORFZJIGF0IFBlYWsiLCAiTkRWSSBhdCBFT1MiLA0KICAgICAgICAgICAgICJEaWZmZXJlbmNlIFBlYWstU09TIiwgIkRpZmZlcmVuY2UgUGVhay1FT1MiLCAiU2Vhc29uIExlbmd0aCIpKQ0KYGBgDQoNCiMgR1BTIHZhbGlkIHBvaW50cyBhYm92ZSBwMjAgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluIGZvciBlYWNoIGhhYml0YXQNCg0KIyBIRVJFISANCg0KQ2hvc2VuIE5EVklfbWluIGJlY2F1c2UgaXQgd2FzIGltcG9ydGFudCBpbiBSRiBtb2RlbHMsIGJ1dCBsZXQncyBzZWUgd2l0aCBuZXcgZGF0YSENCg0KYGBge3J9DQpwZXJjZW50aWxlc19hbGxfR1BTIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGdyb3VwX2J5KEVVTklTYV8xKSAlPiUNCiAgc3VtbWFyaXplKHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgcHJvYnMgPSAwLjIwLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgcGVyY2VudGlsZV8yMF9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCBwcm9icyA9IDAuMjAsIG5hLnJtID0gVCkpDQoNCmFsbF9HUFNfdmFsaWQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgbGVmdF9qb2luKHBlcmNlbnRpbGVzX2FsbF9HUFMsIGJ5ID0gIkVVTklTYV8xIikgJT4lDQogIG11dGF0ZShjYXRlZ29yeV9ORFZJX21heCA9IGNhc2Vfd2hlbigNCiAgICBORFZJX21heCA8IHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggfiAiYmVsb3dfMjB0aCIsDQogICAgTkRWSV9tYXggPj0gcGVyY2VudGlsZV8yMF9ORFZJX21heCB+ICJhYm92ZV8yMHRoIiksDQogIGNhdGVnb3J5X05ETUlfbWluID0gY2FzZV93aGVuKA0KICAgIE5ETUlfbWluIDwgcGVyY2VudGlsZV8yMF9ORE1JX21pbiB+ICJiZWxvd18yMHRoIiwNCiAgICBORE1JX21pbiA+PSBwZXJjZW50aWxlXzIwX05ETUlfbWluIH4gImFib3ZlXzIwdGgiKSkNCg0KZ2dwbG90KGRhdGEgPSBhbGxfR1BTX3ZhbGlkLA0KICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIHkgPSBORFZJX21heCkpICsNCiAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgsDQogICAgICAgICAgICAgICAgICAgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2F0ZWdvcnlfTkRWSV9tYXgpLA0KICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAxLCBhbHBoYSA9IDAuMjUpICsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBzaGFwZSA9IDIwLCBzaXplID0gMSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogIGxhYnMoeSA9ICJORFZJIG1heCIsIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsNCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJiZWxvd18yMHRoIiA9ICJncmV5IiwgImFib3ZlXzIwdGgiID0gImxpZ2h0Ymx1ZSIpKSArDQogIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkNCg0KZ2dwbG90KGRhdGEgPSBhbGxfR1BTX3ZhbGlkLA0KICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIHkgPSBORE1JX21pbikpICsNCiAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgsDQogICAgICAgICAgICAgICAgICAgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2F0ZWdvcnlfTkRNSV9taW4pLA0KICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAxLCBhbHBoYSA9IDAuMjUpICsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBzaGFwZSA9IDIwLCBzaXplID0gMSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogIGxhYnMoeSA9ICJORE1JIG1pbiIsIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsNCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJiZWxvd18yMHRoIiA9ICJncmV5IiwgImFib3ZlXzIwdGgiID0gImxpZ2h0Ymx1ZSIpKSArDQogIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkNCmBgYA0KDQojIFJGIG1vZGVscw0KDQpVc2luZyB0aGUgY29uZGl0aW9uYWwgaW5mZXJlbmNlIHZlcnNpb24gb2YgcmFuZG9tIGZvcmVzdCAoY2ZvcmVzdCBpbiBwYWNrYWdlIHBhcnR5KS4gU3VnZ2VzdGVkIGlmIHRoZSBkYXRhIGFyZSBoaWdobHkgY29ycmVsYXRlZC4gQ2ZvcmVzdCBpcyBtb3JlIHN0YWJsZSBpbiBkZXJpdmluZyB2YXJpYWJsZSBpbXBvcnRhbmNlIHZhbHVlcyBpbiB0aGUgcHJlc2VuY2Ugb2YgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzLCB0aHVzIHByb3ZpZGluZyBiZXR0ZXIgYWNjdXJhY3kgaW4gY2FsY3VsYXRpbmcgdmFyaWFibGUgaW1wb3J0YW5jZSAocmVmIGJlbG93KS4NCg0KSG90aG9ybiwgVC4sIEhvcm5paywgSy4gYW5kIFplaWxlaXMsIEEuICgyMDA2KSBVbmJpYXNlZCBSZWN1cnNpdmUgUG9ydGlvbmluZzogQSBDb25kaXRpb25hbCBJbmZlcmVuY2UgRnJhbWV3b3JrLiBKb3VybmFsIG9mIENvbXB1dGF0aW9uYWwgYW5kIEdyYXBoaWNhbCBTdGF0aXN0aWNzLCAxNSwgNjUxLQ0KNjc0LiBodHRwOi8vZHguZG9pLm9yZy8xMC4xMTk4LzEwNjE4NjAwNlgxMzM5MzMNCg0KIyMgQWxsIEdQUyBwb2ludHMNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBmaWx0ZXIoIWlzLm5hKE5EVklfbWF4KSAmICFpcy5uYShORE1JX21heCkgJiAhaXMubmEoTkRXSV9tYXgpICYNCiAgICAgICAgICAgIWlzLm5hKFNBVklfbWF4KSAmICFpcy5uYShFVklfbWF4KSAmICFpcy5uYShORFZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoTkRNSV9taW4pICYgIWlzLm5hKE5EV0lfbWluKSAmICFpcy5uYShTQVZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoRVZJX21pbikpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczAgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhMCksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTApKQ0KdHJhaW5fZGF0YTAgPC0gZmlsdGVyZWRfZGF0YTBbdHJhaW5faW5kaWNlczAsIF0NCnRlc3RfZGF0YTAgPC0gZmlsdGVyZWRfZGF0YTBbLXRyYWluX2luZGljZXMwLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTAgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KcmZfY2ZvcmVzdDAgPC0gcGFydHk6OmNmb3Jlc3QoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBORE1JX21pbiArIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRVZJX21pbiArIFNBVklfbWF4ICsgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgxMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IG10cnkgPSA1DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQmFnZ2luZzogbXRyeSA9IE5VTEwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvciA9IG51bWJlciBvZiBpbnB1dCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSA1MDApICMgRGVmYXVsdCwgdHJ5IGluY3JlYXNpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MCA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QwLCBuZXdkYXRhID0gdGVzdF9kYXRhMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDAsIHRlc3RfZGF0YTAkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDAgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MCwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDAgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MCwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0MCwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3QwLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDBfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0MCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3QwKQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0MF9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QwLCBuZXdkYXRhID0gdGVzdF9kYXRhMCwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMCRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMCA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzANCmBgYA0KDQojIyBSRVZJU0UgRlJPTSBIRVJFOiBBbGwgR1BTIHBvaW50cyBhYm92ZSBwMjANCg0KRmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgYWJvdmUgcDIwIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIHNlbGVjdCgtcGVyY2VudGlsZV8yMF9ORFZJX21heCwgLXBlcmNlbnRpbGVfMjBfTkRNSV9taW4pDQpgYGANCg0KYGBge3J9DQpwZXJjZW50aWxlcyA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBncm91cF9ieShFVU5JU2FfMSkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBwZXJjZW50aWxlXzIwX05EVklfbWF4ID0gcXVhbnRpbGUoTkRWSV9tYXgsIDAuMjAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV8yMF9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCAwLjIwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfODBfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC44MCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzgwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIDAuODAsIG5hLnJtID0gVCkNCiAgICApDQoNCiMgSm9pbiB0aGUgcGVyY2VudGlsZXMgYmFjayB0byB0aGUgb3JpZ2luYWwgZGF0YQ0KYWxsX0dQU192YWxpZCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBsZWZ0X2pvaW4ocGVyY2VudGlsZXMsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBGaWx0ZXIgcm93cyBhYm92ZSB0aGUgMjB0aCBwZXJjZW50aWxlIGZvciBib3RoIHZhcmlhYmxlcyBmb3IgZWFjaCBjYXRlZ29yeSBvZiBFVU5JU2FfMQ0KZmlsdGVyZWRfZGF0YTEgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZmlsdGVyKA0KICAgIE5EVklfbWF4ID49IHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggJiBORE1JX21pbiA+PSBwZXJjZW50aWxlXzIwX05ETUlfbWluDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXMxIDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTEpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGExKSkNCnRyYWluX2RhdGExIDwtIGZpbHRlcmVkX2RhdGExW3RyYWluX2luZGljZXMxLCBdDQp0ZXN0X2RhdGExIDwtIGZpbHRlcmVkX2RhdGExWy10cmFpbl9pbmRpY2VzMSwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGExICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpJbnZlc3RpZ2F0ZSBwYWNrYWdlIGdncGFydHkgKGUuZy4gYXV0b3Bsb3QgZnVuY3Rpb24sIGFuZCBtb3JlKS4NCg0KVE8tRE86IA0KQ2hvb3NlIHRoZSBoeXBlcnBhcmFtZXRlciBtdHJ5IGJhc2VkIG9uIHRoZSBzcXVhcmUgcm9vdCBvZiB0aGUgbnVtYmVyIG9mIHByZWRpY3RvciB2YXJpYWJsZXMgKEhhc3RpZSBldCBhbC4sIDIwMDkpLQ0KDQpIYXN0aWUsIFQuLCBUaWJzaGlyYW5pLCBSLiwgJiBGcmllZG1hbiwgSi4gKDIwMDkpLiBUaGUgZWxlbWVudHMgb2Ygc3RhdGlzdGljYWwNCmxlYXJuaW5nOiBEYXRhIG1pbmluZywgaW5mZXJlbmNlLCBhbmQgcHJlZGljdGlvbi4gU3ByaW5nZXIgU2NpZW5jZSAmDQpCdXNpbmVzcyBNZWRpYS4NCg0KTWF5YmUgVE9fRE86DQpXZSB2YXJpYXRlZCBudHJlZSBmcm9tIDUwIHRvIDgwMCBpbiBzdGVwcyBvZiA1MCwgbGVhdmluZyBtdHJ5IGNvbnN0YW50IGF0IDIuIFRpcyBwYXJhbWV0ZXIgdmFyaWF0aW9uIHNob3dlZCB0aGF0IG50cmVlPTUwMCB3YXMgb3B0aW1hbCwgd2hpbGUgaGlnaGVyIG50cmVlIGxlZCB0byBubyBmdXJ0aGVyIG1vZGVsIGltcHJvdmVtZW50IChTdXBwbGVtZW50YXJ5IEZpZy4gUzEwKS4gU3Vic2VxdWVudGx5LCB0aGUgaHlwZXJwYXJhbWV0ZXIgbXRyeSB3YXMgdmFyaWVkIGZyb20gMiB0byA4IHdpdGggY29uc3RhbnQgbnRyZWU9NTAwLiBIZXJlLCBtdHJ5PTMgbGVkIHRvIHRoZSBiZXN0IHJlc3VsdHMgaW4gYWxtb3N0IGFsbCBjYXNlcyAoU3VwcGxlbWVudGFyeSBGaWcuIFMxMSkuIENvbnNlcXVlbnRseSwgd2UgY2hvc2UgbnRyZWU9NTAwIGFuZCBtdHJ5PTMgZm9yIG91ciBtYWluIGFuYWx5c2lzIGFjcm9zcyBhbGwgc3R1ZHkgc2l0ZXMuDQoNCmBgYHtyfQ0KcmZfY2ZvcmVzdDEgPC0gcGFydHk6OmNmb3Jlc3QoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBORE1JX21pbiArIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRVZJX21pbiArIFNBVklfbWF4ICsgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgxMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IG10cnkgPSA1DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQmFnZ2luZzogbXRyeSA9IE5VTEwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvciA9IG51bWJlciBvZiBpbnB1dCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSA1MDApICMgRGVmYXVsdCwgdHJ5IGluY3JlYXNpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MSA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDEsIHRlc3RfZGF0YTEkRVVOSVNhXzEpDQpgYGANCg0KU3Vycm9nYXRlVHJlZSAtLT4gZG9lcyBub3Qgd29yaw0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MSA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QxLCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0MSA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QxLCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3QxLCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDEuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MV9kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3QxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDEpDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3QxX2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpUcmVlIFZpc3VhbGl6YXRpb24NCg0KYGBge3J9DQojIENyZWF0ZSBhIHNpbmdsZSBjb25kaXRpb25hbCBpbmZlcmVuY2UgdHJlZSB1c2luZyBjdHJlZQ0Kc2luZ2xlX3RyZWUxIDwtIGN0cmVlKEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsgTkRNSV9taW4gKw0KICAgICAgICAgICAgICAgICAgICAgICBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArIEVWSV9taW4gKyBTQVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwNCiAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhMSkNCg0KIyBQbG90IHRoZSBzaW5nbGUgdHJlZSB1c2luZw0KYXV0b3Bsb3Qoc2luZ2xlX3RyZWUxKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyfQ0KIyBQcmVkaWN0IHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCnByb2JhYmlsaXRpZXMgPC0gcHJlZGljdChyZl9jZm9yZXN0MSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTEsIHR5cGUgPSAicHJvYiIpDQoNCiMgU3RlcCAxOiBDb252ZXJ0IGxpc3Qgb2YgbWF0cmljZXMgdG8gYSBwcm9wZXIgZGF0YSBmcmFtZQ0KcHJvYl9tYXRyaXggPC0gdChzYXBwbHkocHJvYmFiaWxpdGllcywgYXMudmVjdG9yKSkNCmNvbG5hbWVzKHByb2JfbWF0cml4KSA8LSBjKCJRIiwgIlIiLCAiUyIsICJUIikgICMgQWRqdXN0IGlmIG5lZWRlZA0KcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHByb2JfbWF0cml4KQ0KDQojIFN0ZXAgMjogUHJlcGFyZSBhY3R1YWwgY2xhc3MgbGFiZWxzDQphY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YTEkRVVOSVNhXzEsIGxldmVscyA9IGMoIlEiLCAiUiIsICJTIiwgIlQiKSkNCmNsYXNzZXMgPC0gbGV2ZWxzKGFjdHVhbCkNCg0KIyBTdGVwIDM6IEJpbmFyaXplIGFjdHVhbCBsYWJlbHMNCmFjdHVhbF9iaW4gPC0gbW9kZWwubWF0cml4KH4gYWN0dWFsIC0gMSkNCmNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCg0KIyBTdGVwIDQ6IENvbXB1dGUgUk9DIGRhdGEgZm9yIGVhY2ggY2xhc3Mgd2l0aCBBVUMgaW4gbGFiZWwNCnJvY19kYXRhIDwtIGxhcHBseShjbGFzc2VzLCBmdW5jdGlvbihjbGFzcykgew0KICByb2Nfb2JqIDwtIHJvYyhhY3R1YWxfYmluWywgY2xhc3NdLCBwcm9iX2RmW1tjbGFzc11dKQ0KICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgZGF0YS5mcmFtZSgNCiAgICBGUFIgPSByZXYocm9jX29iaiRzcGVjaWZpY2l0aWVzKSwNCiAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICBDbGFzcyA9IHBhc3RlMChjbGFzcywgIiAoQVVDID0gIiwgYXVjX3ZhbCwgIikiKQ0KICApDQp9KSAlPiUgYmluZF9yb3dzKCkNCg0KIyBTdGVwIDU6IFBsb3QgUk9DIGN1cnZlcyB3aXRoIGdncGxvdDINCnJvYzEgPC0gZ2dwbG90KHJvY19kYXRhLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2MxDQpgYGANCg0KDQojIyBBbGwgR1BTIHBvaW50cyB3aXRoaW4gSVEgcmFuZ2UNCg0KRmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgd2l0aGluIElRIHJhbmdlIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQpJUV9yYW5nZXMgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUoDQogICAgUTFfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC4yNSwgbmEucm0gPSBUKSwNCiAgICBRMV9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCAwLjI1LCBuYS5ybSA9IFQpLA0KICAgIFEzX05EVklfbWF4ID0gcXVhbnRpbGUoTkRWSV9tYXgsIDAuNzUsIG5hLnJtID0gVCksDQogICAgUTNfTkRNSV9taW4gPSBxdWFudGlsZShORE1JX21pbiwgMC43NSwgbmEucm0gPSBUKSwNCiAgICBJUVJfTkRWSV9tYXggPSBJUVIoTkRWSV9tYXgsIG5hLnJtID0gVFJVRSksDQogICAgSVFSX05ETUlfbWluID0gSVFSKE5ETUlfbWluLCBuYS5ybSA9IFRSVUUpDQogICAgKQ0KDQojIEpvaW4gdGhlIElRIHJhbmdlcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCBkYXRhDQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGxlZnRfam9pbihJUV9yYW5nZXMsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBGaWx0ZXIgcm93cyB3aXRoaW4gdGhlIElRUiByYW5nZSBmb3IgYm90aCB2YXJpYWJsZXMNCmZpbHRlcmVkX2RhdGEyIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGZpbHRlcigNCiAgICAoTkRWSV9tYXggPj0gUTFfTkRWSV9tYXggJiBORFZJX21heCA8PSBRM19ORFZJX21heCkgJg0KICAgIChORE1JX21pbiA+PSBRMV9ORE1JX21pbiAmIE5ETUlfbWluIDw9IFEzX05ETUlfbWluKQ0KICAgICkgJT4lDQogIGZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMiA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGEyKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMikpDQp0cmFpbl9kYXRhMiA8LSBmaWx0ZXJlZF9kYXRhMlt0cmFpbl9pbmRpY2VzMiwgXQ0KdGVzdF9kYXRhMiA8LSBmaWx0ZXJlZF9kYXRhMlstdHJhaW5faW5kaWNlczIsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMiAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MiA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QyIDwtIHByZWRpY3QocmZfY2ZvcmVzdDIsIG5ld2RhdGEgPSB0ZXN0X2RhdGEyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MiwgdGVzdF9kYXRhMiRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MiA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QyLCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0MiA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QyLCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3QyLCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDIuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0Ml9kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3QyKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDIpDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3QyX2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7cn0NCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciBlYWNoIGNsYXNzDQpwcm9iYWJpbGl0aWVzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLCB0eXBlID0gInByb2IiKQ0KDQojIFN0ZXAgMTogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCnByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQpjb2xuYW1lcyhwcm9iX21hdHJpeCkgPC0gYygiUSIsICJSIiwgIlMiLCAiVCIpICAjIEFkanVzdCBpZiBuZWVkZWQNCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCg0KIyBTdGVwIDI6IFByZXBhcmUgYWN0dWFsIGNsYXNzIGxhYmVscw0KYWN0dWFsIDwtIGZhY3Rvcih0ZXN0X2RhdGExJEVVTklTYV8xLCBsZXZlbHMgPSBjKCJRIiwgIlIiLCAiUyIsICJUIikpDQpjbGFzc2VzIDwtIGxldmVscyhhY3R1YWwpDQoNCiMgU3RlcCAzOiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQphY3R1YWxfYmluIDwtIG1vZGVsLm1hdHJpeCh+IGFjdHVhbCAtIDEpDQpjb2xuYW1lcyhhY3R1YWxfYmluKSA8LSBnc3ViKCJhY3R1YWwiLCAiIiwgY29sbmFtZXMoYWN0dWFsX2JpbikpDQoNCiMgU3RlcCA0OiBDb21wdXRlIFJPQyBkYXRhIGZvciBlYWNoIGNsYXNzIHdpdGggQVVDIGluIGxhYmVsDQpyb2NfZGF0YSA8LSBsYXBwbHkoY2xhc3NlcywgZnVuY3Rpb24oY2xhc3MpIHsNCiAgcm9jX29iaiA8LSByb2MoYWN0dWFsX2JpblssIGNsYXNzXSwgcHJvYl9kZltbY2xhc3NdXSkNCiAgYXVjX3ZhbCA8LSByb3VuZChhdWMocm9jX29iaiksIDMpDQogIGRhdGEuZnJhbWUoDQogICAgRlBSID0gcmV2KHJvY19vYmokc3BlY2lmaWNpdGllcyksDQogICAgVFBSID0gcmV2KHJvY19vYmokc2Vuc2l0aXZpdGllcyksDQogICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgKQ0KfSkgJT4lIGJpbmRfcm93cygpDQoNCiMgU3RlcCA1OiBQbG90IFJPQyBjdXJ2ZXMgd2l0aCBnZ3Bsb3QyDQpyb2MyIDwtIGdncGxvdChyb2NfZGF0YSwgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jMg0KYGBgDQoNCiMjIEFsbCBHUFMgcG9pbnRzIHdpdGhpbiAxLjUgKiBJUSByYW5nZQ0KDQpGaWx0ZXIgdGhlIGRhdGEgdG8gZ2V0IG9ubHkgR1BTLXBvaW50cyB3aXRoaW4gMS41ICogSVEgcmFuZ2Ugb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCiMgRmlsdGVyIHJvd3Mgd2l0aGluIHRoZSAxLjUgKiBJUVIgcmFuZ2UgZm9yIGJvdGggdmFyaWFibGVzDQpmaWx0ZXJlZF9kYXRhMyA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBmaWx0ZXIoDQogICAgKE5EVklfbWF4ID49IChRMV9ORFZJX21heCAtIDEuNSAqIElRUl9ORFZJX21heCkgJiBORFZJX21heCA8PSAoUTNfTkRWSV9tYXggKyAxLjUgKiBJUVJfTkRWSV9tYXgpKSAmDQogICAgICAoTkRNSV9taW4gPj0gKFExX05ETUlfbWluIC0gMS41ICogSVFSX05ETUlfbWluKSAmIE5ETUlfbWluIDw9IChRM19ORE1JX21pbiArIDEuNSAqIElRUl9ORE1JX21pbikpDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXMzIDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTMpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGEzKSkNCnRyYWluX2RhdGEzIDwtIGZpbHRlcmVkX2RhdGEzW3RyYWluX2luZGljZXMzLCBdDQp0ZXN0X2RhdGEzIDwtIGZpbHRlcmVkX2RhdGEzWy10cmFpbl9pbmRpY2VzMywgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEzICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnJmX2Nmb3Jlc3QzIDwtIHBhcnR5OjpjZm9yZXN0KEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkRNSV9taW4gKyBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVWSV9taW4gKyBTQVZJX21heCArIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YTMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbXRyeSA9IHNxcnQoMTEpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfcmZfY2ZvcmVzdDMgPC0gcHJlZGljdChyZl9jZm9yZXN0MywgbmV3ZGF0YSA9IHRlc3RfZGF0YTMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9PQiA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QzLCB0ZXN0X2RhdGEzJEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QzIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDMsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3QzIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDMsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDMsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0My5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QzX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0MykNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDNfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyfQ0KIyBQcmVkaWN0IHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCnByb2JhYmlsaXRpZXMgPC0gcHJlZGljdChyZl9jZm9yZXN0MSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTEsIHR5cGUgPSAicHJvYiIpDQoNCiMgU3RlcCAxOiBDb252ZXJ0IGxpc3Qgb2YgbWF0cmljZXMgdG8gYSBwcm9wZXIgZGF0YSBmcmFtZQ0KcHJvYl9tYXRyaXggPC0gdChzYXBwbHkocHJvYmFiaWxpdGllcywgYXMudmVjdG9yKSkNCmNvbG5hbWVzKHByb2JfbWF0cml4KSA8LSBjKCJRIiwgIlIiLCAiUyIsICJUIikgICMgQWRqdXN0IGlmIG5lZWRlZA0KcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHByb2JfbWF0cml4KQ0KDQojIFN0ZXAgMjogUHJlcGFyZSBhY3R1YWwgY2xhc3MgbGFiZWxzDQphY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YTEkRVVOSVNhXzEsIGxldmVscyA9IGMoIlEiLCAiUiIsICJTIiwgIlQiKSkNCmNsYXNzZXMgPC0gbGV2ZWxzKGFjdHVhbCkNCg0KIyBTdGVwIDM6IEJpbmFyaXplIGFjdHVhbCBsYWJlbHMNCmFjdHVhbF9iaW4gPC0gbW9kZWwubWF0cml4KH4gYWN0dWFsIC0gMSkNCmNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCg0KIyBTdGVwIDQ6IENvbXB1dGUgUk9DIGRhdGEgZm9yIGVhY2ggY2xhc3Mgd2l0aCBBVUMgaW4gbGFiZWwNCnJvY19kYXRhIDwtIGxhcHBseShjbGFzc2VzLCBmdW5jdGlvbihjbGFzcykgew0KICByb2Nfb2JqIDwtIHJvYyhhY3R1YWxfYmluWywgY2xhc3NdLCBwcm9iX2RmW1tjbGFzc11dKQ0KICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgZGF0YS5mcmFtZSgNCiAgICBGUFIgPSByZXYocm9jX29iaiRzcGVjaWZpY2l0aWVzKSwNCiAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICBDbGFzcyA9IHBhc3RlMChjbGFzcywgIiAoQVVDID0gIiwgYXVjX3ZhbCwgIikiKQ0KICApDQp9KSAlPiUgYmluZF9yb3dzKCkNCg0KIyBTdGVwIDU6IFBsb3QgUk9DIGN1cnZlcyB3aXRoIGdncGxvdDINCnJvYzMgPC0gZ2dwbG90KHJvY19kYXRhLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2MzDQpgYGANCg0KIyMgQWxsIEdQUyBwb2ludHMgd2l0aGluIG1lYW4gKy8tIFNEDQoNCkZpbHRlciB0aGUgZGF0YSB0byBnZXQgb25seSBHUFMtcG9pbnRzIHdpdGhpbiBtZWFuICsvLSBTRCBvZiBORFZJX21heCBhbmQgTkRNSV9taW4uDQoNCmBgYHtyfQ0KbWVhbl9zZCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBncm91cF9ieShFVU5JU2FfMSkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBtZWFuX05EVklfbWF4ID0gbWVhbihhbGxfR1BTX3ZhbGlkJE5EVklfbWF4LCBuYS5ybSA9IFQpLA0KICAgIG1lYW5fTkRNSV9taW4gPSBtZWFuKGFsbF9HUFNfdmFsaWQkTkRNSV9taW4sIG5hLnJtID0gVCksDQogICAgc2RfTkRWSV9tYXggPSBzZChhbGxfR1BTX3ZhbGlkJE5EVklfbWF4LCBuYS5ybSA9IFQpLA0KICAgIHNkX05ETUlfbWluID0gc2QoYWxsX0dQU192YWxpZCRORE1JX21pbiwgbmEucm0gPSBUKQ0KICAgICkNCg0KIyBKb2luIHRoZSBJUSByYW5nZXMgYmFjayB0byB0aGUgb3JpZ2luYWwgZGF0YQ0KYWxsX0dQU192YWxpZCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBsZWZ0X2pvaW4obWVhbl9zZCwgYnkgPSAiRVVOSVNhXzEiKQ0KDQojIEZpbHRlciByb3dzIHdpdGhpbiB0aGUgc3BlY2lmaWVkIHJhbmdlIGZvciBib3RoIHZhcmlhYmxlcw0KZmlsdGVyZWRfZGF0YTQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZmlsdGVyKA0KICAgIChORFZJX21heCA+PSAobWVhbl9ORFZJX21heCAtIHNkX05EVklfbWF4KSAmIE5EVklfbWF4IDw9IChtZWFuX05EVklfbWF4ICsgc2RfTkRWSV9tYXgpKSAmDQogICAgICAoTkRNSV9taW4gPj0gKG1lYW5fTkRNSV9taW4gLSBzZF9ORE1JX21pbikgJiBORE1JX21pbiA8PSAobWVhbl9ORE1JX21pbiArIHNkX05ETUlfbWluKSkNCiAgICApICU+JQ0KICBmaWx0ZXIoIWlzLm5hKE5EVklfbWF4KSAmICFpcy5uYShORE1JX21heCkgJiAhaXMubmEoTkRXSV9tYXgpICYNCiAgICAgICAgICAgIWlzLm5hKFNBVklfbWF4KSAmICFpcy5uYShFVklfbWF4KSAmICFpcy5uYShORFZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoTkRNSV9taW4pICYgIWlzLm5hKE5EV0lfbWluKSAmICFpcy5uYShTQVZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoRVZJX21pbikpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczQgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhNCksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTQpKQ0KdHJhaW5fZGF0YTQgPC0gZmlsdGVyZWRfZGF0YTRbdHJhaW5faW5kaWNlczQsIF0NCnRlc3RfZGF0YTQgPC0gZmlsdGVyZWRfZGF0YTRbLXRyYWluX2luZGljZXM0LCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTQgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KcmZfY2ZvcmVzdDQgPC0gcGFydHk6OmNmb3Jlc3QoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBORE1JX21pbiArIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRVZJX21pbiArIFNBVklfbWF4ICsgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhNCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgxMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IG10cnkgPSA1DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQmFnZ2luZzogbXRyeSA9IE5VTEwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvciA9IG51bWJlciBvZiBpbnB1dCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSA1MDApICMgRGVmYXVsdCwgdHJ5IGluY3JlYXNpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0NCA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3Q0LCBuZXdkYXRhID0gdGVzdF9kYXRhNCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDQsIHRlc3RfZGF0YTQkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDQgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0NCwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDQgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0NCwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0NCwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3Q0LlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDRfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0NCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3Q0KQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0NF9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jNCA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzQNCmBgYA0KDQojIyBBbGwgR1BTIHBvaW50cyBhYm92ZSBwMjAgYW5kIGJlbG93IHA4MA0KDQpGaWx0ZXIgdGhlIGRhdGEgdG8gZ2V0IG9ubHkgR1BTLXBvaW50cyBhYm92ZSBwMjAgYW5kIGJlbG93IHA4MCBvZiBORFZJX21heCBhbmQgTkRNSV9taW4uDQoNCmBgYHtyfQ0KIyBGaWx0ZXIgcm93cyBhYm92ZSB0aGUgMjB0aCBwZXJjZW50aWxlIGFuZCBiZWxvdyB0aGUgODB0aCBwZXJjZW50aWxlIGZvciBib3RoIHZhcmlhYmxlcw0KZmlsdGVyZWRfZGF0YTUgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZmlsdGVyKA0KICAgIChORFZJX21heCA+PSBwZXJjZW50aWxlXzIwX05EVklfbWF4ICYgTkRWSV9tYXggPD0gcGVyY2VudGlsZV84MF9ORFZJX21heCkgJg0KICAgIChORE1JX21pbiA+PSBwZXJjZW50aWxlXzIwX05ETUlfbWluICYgTkRNSV9taW4gPD0gcGVyY2VudGlsZV84MF9ORE1JX21pbikNCiAgICApICU+JQ0KICBmaWx0ZXIoIWlzLm5hKE5EVklfbWF4KSAmICFpcy5uYShORE1JX21heCkgJiAhaXMubmEoTkRXSV9tYXgpICYNCiAgICAgICAgICAgIWlzLm5hKFNBVklfbWF4KSAmICFpcy5uYShFVklfbWF4KSAmICFpcy5uYShORFZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoTkRNSV9taW4pICYgIWlzLm5hKE5EV0lfbWluKSAmICFpcy5uYShTQVZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoRVZJX21pbikpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczUgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhNSksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTUpKQ0KdHJhaW5fZGF0YTUgPC0gZmlsdGVyZWRfZGF0YTVbdHJhaW5faW5kaWNlczUsIF0NCnRlc3RfZGF0YTUgPC0gZmlsdGVyZWRfZGF0YTVbLXRyYWluX2luZGljZXM1LCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTUgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KcmZfY2ZvcmVzdDUgPC0gcGFydHk6OmNmb3Jlc3QoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBORE1JX21pbiArIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRVZJX21pbiArIFNBVklfbWF4ICsgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgxMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IG10cnkgPSA1DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQmFnZ2luZzogbXRyeSA9IE5VTEwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvciA9IG51bWJlciBvZiBpbnB1dCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSA1MDApICMgRGVmYXVsdCwgdHJ5IGluY3JlYXNpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0NSA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3Q1LCBuZXdkYXRhID0gdGVzdF9kYXRhNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDUsIHRlc3RfZGF0YTUkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDUgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0NSwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDUgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0NSwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0NSwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3Q1LlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDVfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0NSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3Q1KQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0NV9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jNSA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzUNCmBgYA0KDQojIEhFUkU6IENvbXBhcmUgUkYgMS01DQoNCiMgQ29yZGlsbGVyYSBkYXRhDQoNCmBgYHtyfQ0KQWxwaW5lR3Jhc3NsYW5kc19pbmRpY2VzIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL0FscGluZUdyYXNzbGFuZHMvQWxwaW5lR3Jhc3NsYW5kX1NlbnRpbmVsX1Bsb3RfQWxseWVhcl9BbGxtZXRyaWNzLmNzdiIpDQpBbHBpbmVHcmFzc2xhbmRzX3BoZW4gPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvQWxwaW5lR3Jhc3NsYW5kcy9BbHBpbmVHcmFzc2xhbmRzX1BoZW5vbG9neV9TT1NfRU9TX1BlYWtfTkRWSV9BbXBsaXR1ZGUuY3N2IikNCkFscGluZUdyYXNzbGFuZHNfQ0ggPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvQWxwaW5lR3Jhc3NsYW5kcy9BbHBpbmVHcmFzc2xhbmRzX0Nhbm9weUhlaWdodF8xbS5jc3YiKQ0KVmVnZXRhdGlvblR5cGVzX2luZGljZXMgPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvVmVnZXRhdGlvblR5cGVzL1ZlZ2V0YXRpb25UeXBlc19TZW50aW5lbF9QbG90X0FsbFllYXJfQWxsbWV0cmljcy5jc3YiKQ0KVmVnZXRhdGlvblR5cGVzX3BoZW4gPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvVmVnZXRhdGlvblR5cGVzL1ZlZ2V0YXRpb25UeXBlc19QaGVub2xvZ3lfU09TX0VPU19QZWFrX05EVklfQW1wbGl0dWRlLmNzdiIpDQpWZWdldGF0aW9uVHlwZXNfQ0ggPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvVmVnZXRhdGlvblR5cGVzL1ZlZ2V0YXRpb25UeXBlc19DYW5vcHlIZWlnaHRfMW0uY3N2IikNCmBgYA0KDQpgYGB7cn0NCkFscGluZUdyYXNzbGFuZHMgPC0gQWxwaW5lR3Jhc3NsYW5kc19pbmRpY2VzICU+JQ0KICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbywgLUxvY2FsaWRhZCkgJT4lDQogIHJlbmFtZShIw6FiaXRhdCA9ICJI77+9Yml0YXQiKSAlPiUgDQogIGZ1bGxfam9pbihBbHBpbmVHcmFzc2xhbmRzX3BoZW4gICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbywgLUxvY2FsaWRhZCkgJT4lDQogICAgICAgICAgICAgIHJlbmFtZShIw6FiaXRhdCA9ICJI77+9Yml0YXQiKSkgJT4lDQogIGZ1bGxfam9pbihBbHBpbmVHcmFzc2xhbmRzX0NIICAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8sIC1Mb2NhbGlkYWQpKSAlPiUNCiAgc2VsZWN0KC1EYXRlX195ZWFyLCAtIGBQcmVjaXNp77+9bmApICU+JQ0KICBtdXRhdGUoREFURSA9IHltZChEQVRFKSkgJT4lDQogIHJlbmFtZShJRCA9ICJSZWxldmVfbnVtIikgJT4lDQogIG11dGF0ZShJRCA9IGFzLmNoYXJhY3RlcihJRCkpICU+JQ0KICBtdXRhdGUobGF5ZXIgPSAiQWxwaW5lR3Jhc3NsYW5kcyIpDQpgYGANCg0KYGBge3J9DQpWZWdldGF0aW9uVHlwZXMgPC0gVmVnZXRhdGlvblR5cGVzX2luZGljZXMgJT4lDQogIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvKSAlPiUNCiAgZnVsbF9qb2luKFZlZ2V0YXRpb25UeXBlc19waGVuICAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8pKSAlPiUNCiAgZnVsbF9qb2luKFZlZ2V0YXRpb25UeXBlc19DSCAgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvKSkgJT4lDQogIHJlbmFtZShIw6FiaXRhdCA9ICJUWVBFIikgJT4lDQogIG11dGF0ZShsYXllciA9ICJWZWdldGF0aW9uVHlwZXMiKQ0KYGBgDQoNCk1lcmdlIGJvdGggZGF0YXNldHM6DQoNCmBgYHtyfQ0KY29yZGlsbGVyYSA8LSBiaW5kX3Jvd3MoDQogIEFscGluZUdyYXNzbGFuZHMgJT4lIHNlbGVjdChEQVRFLCBJRCwgc3RhcnRzX3dpdGgoIk5ETUkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0c193aXRoKCJORFZJIiksIEjDoWJpdGF0LCAiRU9TX0RPWSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUGVha19ET1kiLCAiU09TX0RPWSIsICJTZWFzb25fTGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYW5vcHlfaGVpZ2h0IiwgImxheWVyIiksDQogIFZlZ2V0YXRpb25UeXBlcyAlPiUgc2VsZWN0KERBVEUsIElELCBzdGFydHNfd2l0aCgiTkRNSSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRzX3dpdGgoIk5EVkkiKSwgSMOhYml0YXQsICJFT1NfRE9ZIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQZWFrX0RPWSIsICJTT1NfRE9ZIiwgIlNlYXNvbl9MZW5ndGgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNhbm9weV9oZWlnaHQiLCAibGF5ZXIiKQ0KICApICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBjYXNlX3doZW4oDQogICAgSMOhYml0YXQgPSBzdHJfZGV0ZWN0KEjDoWJpdGF0LCAiUGFzdGl6YWx8Q2VydnVuYWx8Z3Jhc3NsYW5kfG1lYWRvdyIpIH4gIlIiLA0KICAgIEjDoWJpdGF0ID0gc3RyX2RldGVjdChIw6FiaXRhdCwgImZvcmVzdCIpIH4gIlQiLA0KICAgIEjDoWJpdGF0ID0gc3RyX2RldGVjdChIw6FiaXRhdCwgIlNjcnVifHNjcnVifFNocnVibGFuZHxzaHJ1YmxhbmR8c2hydWJ8SGVhdGhsYW5kIikgfiAiUyIsDQogICAgSMOhYml0YXQgPSBzdHJfZGV0ZWN0KEjDoWJpdGF0LCAiU3VlbG98U2NyZWV8c2NyZWV8Y2xpZmYiKSB+ICJVIiwNCiAgICBIw6FiaXRhdCA9IGlzLm5hKEjDoWJpdGF0KSB+ICJSIiwNCiAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXyksDQogICAgRVVOSVNhXzFfZGVzY3IgPSBjYXNlX3doZW4oDQogICAgICBFVU5JU2FfMSA9PSAiUiIgfiAiR3Jhc3NsYW5kcyIsDQogICAgICBFVU5JU2FfMSA9PSAiVCIgfiAiRm9yZXN0cyBhbmQgb3RoZXIgd29vZGVkIGxhbmQiLA0KICAgICAgRVVOSVNhXzEgPT0gIlMiIH4gIkhlYXRobGFuZHMsIHNjcnViIGFuZCB0dW5kcmEiLA0KICAgICAgRVVOSVNhXzEgPT0gIlUiIH4gIklubGFuZCBoYWJpdGF0cyB3aXRoIG5vIG9yIGxpdHRsZSBzb2lsIikNCiAgICApDQpgYGANCg0KIyMgTkRWSSwgTkRNSQ0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoY29yZGlsbGVyYSwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiTkRWSV9wOTAiLCAiTkRWSV9taW4iLCAiTkRWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EVkkgbWF4IiwgIk5EVkkgcDkwIiwgIk5EVkkgbWluIiwgIk5EVkkgcDEwIikpDQpkaXN0cl9wbG90KGNvcmRpbGxlcmEsDQogICAgICAgICAgIGMoIk5ETUlfbWF4IiwgIk5ETUlfcDkwIiwgIk5ETUlfbWluIiwgIk5ETUlfcDEwIiksIA0KICAgICAgICAgICBjKCJORE1JIG1heCIsICJORE1JIHA5MCIsICJORE1JIG1pbiIsICJORE1JIHAxMCIpKQ0KYGBgDQoNCiMgU2Vzc2lvbiBpbmZvDQoNCmBgYHtyfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCg==